tiphys.routing
Tiphys multi-agent routing system.
Provides agent registry, bindings, and route resolution for multi-agent support.
1""" 2Tiphys multi-agent routing system. 3 4Provides agent registry, bindings, and route resolution for multi-agent support. 5""" 6 7from tiphys.routing.bindings import ( 8 AgentBinding, 9 BindingPriority, 10 BindingType, 11 RoutingContext, 12 create_channel_binding, 13 create_default_binding, 14 create_group_binding, 15 create_peer_binding, 16) 17from tiphys.routing.registry import ( 18 AgentDefinition, 19 AgentRegistry, 20 create_default_agent, 21 get_agent_registry, 22) 23from tiphys.routing.resolver import ( 24 RouteResolver, 25 get_route_resolver, 26 resolve_agent, 27) 28 29__all__ = [ 30 # Bindings 31 "AgentBinding", 32 "BindingType", 33 "BindingPriority", 34 "RoutingContext", 35 "create_peer_binding", 36 "create_group_binding", 37 "create_channel_binding", 38 "create_default_binding", 39 # Registry 40 "AgentDefinition", 41 "AgentRegistry", 42 "get_agent_registry", 43 "create_default_agent", 44 # Resolver 45 "RouteResolver", 46 "get_route_resolver", 47 "resolve_agent", 48]
34class AgentBinding(BaseModel): 35 """ 36 A binding that maps a source to an agent. 37 38 Bindings are evaluated in priority order until a match is found. 39 """ 40 41 agent_id: str = Field(..., description="Target agent identifier") 42 binding_type: BindingType = Field(..., description="Type of binding") 43 priority: int = Field( 44 default=0, 45 description="Custom priority (higher = evaluated first). Defaults to type priority.", 46 ) 47 48 # Matching criteria (depending on binding type) 49 peer_id: str | None = Field(default=None, description="Peer ID for PEER bindings") 50 group_id: str | None = Field(default=None, description="Group ID for GROUP bindings") 51 channel_name: str | None = Field(default=None, description="Channel name for CHANNEL bindings") 52 pattern: str | None = Field( 53 default=None, description="Pattern for PATTERN bindings (supports glob)" 54 ) 55 56 # Metadata 57 description: str | None = Field(default=None, description="Human-readable description") 58 enabled: bool = Field(default=True, description="Whether this binding is active") 59 metadata: dict[str, Any] = Field(default_factory=dict, description="Extra metadata") 60 61 def model_post_init(self, __context: Any) -> None: 62 """Set default priority based on binding type if not specified.""" 63 if self.priority == 0: 64 self.priority = BindingPriority[self.binding_type.name].value 65 66 def matches( 67 self, 68 peer_id: str | None = None, 69 group_id: str | None = None, 70 channel_name: str | None = None, 71 ) -> bool: 72 """ 73 Check if this binding matches the given context. 74 75 Args: 76 peer_id: The peer ID to match. 77 group_id: The group ID to match. 78 channel_name: The channel name to match. 79 80 Returns: 81 True if this binding matches the context. 82 """ 83 if not self.enabled: 84 return False 85 86 match self.binding_type: 87 case BindingType.PEER: 88 return self.peer_id is not None and self.peer_id == peer_id 89 90 case BindingType.GROUP: 91 return self.group_id is not None and self.group_id == group_id 92 93 case BindingType.CHANNEL: 94 return self.channel_name is not None and self.channel_name == channel_name 95 96 case BindingType.PATTERN: 97 if self.pattern is None: 98 return False 99 # Support glob-style patterns 100 return self._matches_pattern(peer_id, group_id, channel_name) 101 102 case BindingType.DEFAULT: 103 return True 104 105 return False 106 107 def _matches_pattern( 108 self, 109 peer_id: str | None, 110 group_id: str | None, 111 channel_name: str | None, 112 ) -> bool: 113 """Check if any of the identifiers match the pattern.""" 114 import fnmatch 115 116 if self.pattern is None: 117 return False 118 119 # Try to match against any provided identifier 120 for identifier in [peer_id, group_id, channel_name]: 121 if identifier is not None and fnmatch.fnmatch(identifier, self.pattern): 122 return True 123 124 return False
A binding that maps a source to an agent.
Bindings are evaluated in priority order until a match is found.
66 def matches( 67 self, 68 peer_id: str | None = None, 69 group_id: str | None = None, 70 channel_name: str | None = None, 71 ) -> bool: 72 """ 73 Check if this binding matches the given context. 74 75 Args: 76 peer_id: The peer ID to match. 77 group_id: The group ID to match. 78 channel_name: The channel name to match. 79 80 Returns: 81 True if this binding matches the context. 82 """ 83 if not self.enabled: 84 return False 85 86 match self.binding_type: 87 case BindingType.PEER: 88 return self.peer_id is not None and self.peer_id == peer_id 89 90 case BindingType.GROUP: 91 return self.group_id is not None and self.group_id == group_id 92 93 case BindingType.CHANNEL: 94 return self.channel_name is not None and self.channel_name == channel_name 95 96 case BindingType.PATTERN: 97 if self.pattern is None: 98 return False 99 # Support glob-style patterns 100 return self._matches_pattern(peer_id, group_id, channel_name) 101 102 case BindingType.DEFAULT: 103 return True 104 105 return False
Check if this binding matches the given context.
Args: peer_id: The peer ID to match. group_id: The group ID to match. channel_name: The channel name to match.
Returns: True if this binding matches the context.
14class BindingType(str, Enum): 15 """Types of routing bindings.""" 16 17 PEER = "peer" # Direct peer binding 18 GROUP = "group" # Group/channel binding 19 CHANNEL = "channel" # Messaging platform channel 20 PATTERN = "pattern" # Pattern-based binding (regex/glob) 21 DEFAULT = "default" # Fallback binding
Types of routing bindings.
24class BindingPriority(int, Enum): 25 """Priority levels for binding resolution.""" 26 27 PEER = 100 # Highest - direct peer bindings 28 GROUP = 80 # Group-specific bindings 29 CHANNEL = 60 # Channel-specific bindings 30 PATTERN = 40 # Pattern-based bindings 31 DEFAULT = 0 # Lowest - fallback
Priority levels for binding resolution.
127class RoutingContext(BaseModel): 128 """Context for routing decisions.""" 129 130 peer_id: str | None = Field(default=None, description="The peer making the request") 131 group_id: str | None = Field(default=None, description="The group context") 132 channel_name: str | None = Field(default=None, description="The channel name") 133 account_id: str | None = Field(default=None, description="The account ID") 134 metadata: dict[str, Any] = Field(default_factory=dict, description="Extra context")
Context for routing decisions.
137def create_peer_binding(peer_id: str, agent_id: str, **kwargs: Any) -> AgentBinding: 138 """Create a direct peer-to-agent binding.""" 139 return AgentBinding( 140 agent_id=agent_id, 141 binding_type=BindingType.PEER, 142 peer_id=peer_id, 143 **kwargs, 144 )
Create a direct peer-to-agent binding.
147def create_group_binding(group_id: str, agent_id: str, **kwargs: Any) -> AgentBinding: 148 """Create a group-to-agent binding.""" 149 return AgentBinding( 150 agent_id=agent_id, 151 binding_type=BindingType.GROUP, 152 group_id=group_id, 153 **kwargs, 154 )
Create a group-to-agent binding.
157def create_channel_binding(channel_name: str, agent_id: str, **kwargs: Any) -> AgentBinding: 158 """Create a channel-to-agent binding.""" 159 return AgentBinding( 160 agent_id=agent_id, 161 binding_type=BindingType.CHANNEL, 162 channel_name=channel_name, 163 **kwargs, 164 )
Create a channel-to-agent binding.
167def create_default_binding(agent_id: str, **kwargs: Any) -> AgentBinding: 168 """Create a default fallback binding.""" 169 return AgentBinding( 170 agent_id=agent_id, 171 binding_type=BindingType.DEFAULT, 172 **kwargs, 173 )
Create a default fallback binding.
16class AgentDefinition(BaseModel): 17 """ 18 Definition for an agent. 19 20 Each agent has its own identity, configuration, and behavior settings. 21 """ 22 23 id: str = Field(..., description="Unique agent identifier") 24 name: str = Field(..., description="Display name for the agent") 25 description: str | None = Field(default=None, description="Agent description") 26 27 # LLM Configuration 28 model: str | None = Field( 29 default=None, 30 description="LLM model (defaults to global config if not set)", 31 ) 32 api_base: str | None = Field( 33 default=None, 34 description="Custom API base URL for this agent", 35 ) 36 api_key: str | None = Field( 37 default=None, 38 description="API key for this agent's LLM provider", 39 ) 40 temperature: float = Field( 41 default=0.7, 42 ge=0.0, 43 le=2.0, 44 description="LLM temperature", 45 ) 46 max_tokens: int | None = Field( 47 default=None, 48 description="Maximum response tokens", 49 ) 50 51 # Session settings 52 dm_scope: DmScope = Field( 53 default=DmScope.MAIN, 54 description="Session scoping mode for this agent", 55 ) 56 57 # Prompt customization 58 system_prompt: str | None = Field( 59 default=None, 60 description="Custom system prompt (replaces default)", 61 ) 62 system_prompt_prefix: str | None = Field( 63 default=None, 64 description="Prepended to system prompt", 65 ) 66 system_prompt_suffix: str | None = Field( 67 default=None, 68 description="Appended to system prompt", 69 ) 70 custom_instructions: str | None = Field( 71 default=None, 72 description="Additional instructions injected into prompt", 73 ) 74 75 # Tool configuration 76 tools_allow: list[str] = Field( 77 default_factory=list, 78 description="Allowed tools (empty = all tools)", 79 ) 80 tools_deny: list[str] = Field( 81 default_factory=list, 82 description="Denied tools", 83 ) 84 tools_require_permission: list[str] = Field( 85 default_factory=list, 86 description="Tools requiring explicit permission", 87 ) 88 89 # Skills configuration 90 skills_dir: Path | None = Field( 91 default=None, 92 description="Agent-specific skills directory", 93 ) 94 preload_skills: list[str] = Field( 95 default_factory=list, 96 description="Skills to preload into context", 97 ) 98 99 # Behavioral settings 100 max_steps: int = Field( 101 default=10, 102 ge=1, 103 le=100, 104 description="Maximum tool execution steps", 105 ) 106 streaming: bool = Field( 107 default=True, 108 description="Whether to stream responses", 109 ) 110 111 # Metadata 112 metadata: dict[str, Any] = Field( 113 default_factory=dict, 114 description="Extra metadata", 115 ) 116 enabled: bool = Field( 117 default=True, 118 description="Whether this agent is active", 119 )
Definition for an agent.
Each agent has its own identity, configuration, and behavior settings.
122class AgentRegistry: 123 """ 124 Registry for managing multiple agent definitions. 125 126 Provides lookup, validation, and lifecycle management for agents. 127 """ 128 129 def __init__(self) -> None: 130 """Initialize an empty agent registry.""" 131 self._agents: dict[str, AgentDefinition] = {} 132 self._default_agent_id: str | None = None 133 134 def register(self, agent: AgentDefinition) -> None: 135 """ 136 Register an agent definition. 137 138 Args: 139 agent: The agent definition to register. 140 141 Raises: 142 ValueError: If an agent with the same ID already exists. 143 """ 144 if agent.id in self._agents: 145 raise ValueError(f"Agent '{agent.id}' already registered") 146 self._agents[agent.id] = agent 147 148 # Set as default if it's the first agent 149 if self._default_agent_id is None: 150 self._default_agent_id = agent.id 151 152 def unregister(self, agent_id: str) -> bool: 153 """ 154 Unregister an agent. 155 156 Args: 157 agent_id: The agent ID to unregister. 158 159 Returns: 160 True if the agent was unregistered, False if not found. 161 """ 162 if agent_id not in self._agents: 163 return False 164 165 del self._agents[agent_id] 166 167 # Clear default if we removed it 168 if self._default_agent_id == agent_id: 169 self._default_agent_id = next(iter(self._agents), None) 170 171 return True 172 173 def get(self, agent_id: str) -> AgentDefinition | None: 174 """ 175 Get an agent definition by ID. 176 177 Args: 178 agent_id: The agent ID. 179 180 Returns: 181 The agent definition, or None if not found. 182 """ 183 return self._agents.get(agent_id) 184 185 def get_or_default(self, agent_id: str | None = None) -> AgentDefinition | None: 186 """ 187 Get an agent by ID, or return the default agent. 188 189 Args: 190 agent_id: Optional agent ID. Uses default if None. 191 192 Returns: 193 The agent definition, or None if no agents registered. 194 """ 195 if agent_id: 196 agent = self._agents.get(agent_id) 197 if agent: 198 return agent 199 200 if self._default_agent_id: 201 return self._agents.get(self._default_agent_id) 202 203 return None 204 205 def list_agents(self) -> list[AgentDefinition]: 206 """ 207 List all registered agents. 208 209 Returns: 210 List of all agent definitions. 211 """ 212 return list(self._agents.values()) 213 214 def list_ids(self) -> list[str]: 215 """ 216 List all registered agent IDs. 217 218 Returns: 219 List of agent IDs. 220 """ 221 return list(self._agents.keys()) 222 223 def set_default(self, agent_id: str) -> None: 224 """ 225 Set the default agent. 226 227 Args: 228 agent_id: The agent ID to set as default. 229 230 Raises: 231 ValueError: If the agent is not registered. 232 """ 233 if agent_id not in self._agents: 234 raise ValueError(f"Agent '{agent_id}' not registered") 235 self._default_agent_id = agent_id 236 237 @property 238 def default_agent_id(self) -> str | None: 239 """Get the default agent ID.""" 240 return self._default_agent_id 241 242 def clear(self) -> None: 243 """Remove all registered agents.""" 244 self._agents.clear() 245 self._default_agent_id = None 246 247 def __len__(self) -> int: 248 """Return the number of registered agents.""" 249 return len(self._agents) 250 251 def __contains__(self, agent_id: str) -> bool: 252 """Check if an agent is registered.""" 253 return agent_id in self._agents
Registry for managing multiple agent definitions.
Provides lookup, validation, and lifecycle management for agents.
129 def __init__(self) -> None: 130 """Initialize an empty agent registry.""" 131 self._agents: dict[str, AgentDefinition] = {} 132 self._default_agent_id: str | None = None
Initialize an empty agent registry.
134 def register(self, agent: AgentDefinition) -> None: 135 """ 136 Register an agent definition. 137 138 Args: 139 agent: The agent definition to register. 140 141 Raises: 142 ValueError: If an agent with the same ID already exists. 143 """ 144 if agent.id in self._agents: 145 raise ValueError(f"Agent '{agent.id}' already registered") 146 self._agents[agent.id] = agent 147 148 # Set as default if it's the first agent 149 if self._default_agent_id is None: 150 self._default_agent_id = agent.id
Register an agent definition.
Args: agent: The agent definition to register.
Raises: ValueError: If an agent with the same ID already exists.
152 def unregister(self, agent_id: str) -> bool: 153 """ 154 Unregister an agent. 155 156 Args: 157 agent_id: The agent ID to unregister. 158 159 Returns: 160 True if the agent was unregistered, False if not found. 161 """ 162 if agent_id not in self._agents: 163 return False 164 165 del self._agents[agent_id] 166 167 # Clear default if we removed it 168 if self._default_agent_id == agent_id: 169 self._default_agent_id = next(iter(self._agents), None) 170 171 return True
Unregister an agent.
Args: agent_id: The agent ID to unregister.
Returns: True if the agent was unregistered, False if not found.
173 def get(self, agent_id: str) -> AgentDefinition | None: 174 """ 175 Get an agent definition by ID. 176 177 Args: 178 agent_id: The agent ID. 179 180 Returns: 181 The agent definition, or None if not found. 182 """ 183 return self._agents.get(agent_id)
Get an agent definition by ID.
Args: agent_id: The agent ID.
Returns: The agent definition, or None if not found.
185 def get_or_default(self, agent_id: str | None = None) -> AgentDefinition | None: 186 """ 187 Get an agent by ID, or return the default agent. 188 189 Args: 190 agent_id: Optional agent ID. Uses default if None. 191 192 Returns: 193 The agent definition, or None if no agents registered. 194 """ 195 if agent_id: 196 agent = self._agents.get(agent_id) 197 if agent: 198 return agent 199 200 if self._default_agent_id: 201 return self._agents.get(self._default_agent_id) 202 203 return None
Get an agent by ID, or return the default agent.
Args: agent_id: Optional agent ID. Uses default if None.
Returns: The agent definition, or None if no agents registered.
205 def list_agents(self) -> list[AgentDefinition]: 206 """ 207 List all registered agents. 208 209 Returns: 210 List of all agent definitions. 211 """ 212 return list(self._agents.values())
List all registered agents.
Returns: List of all agent definitions.
214 def list_ids(self) -> list[str]: 215 """ 216 List all registered agent IDs. 217 218 Returns: 219 List of agent IDs. 220 """ 221 return list(self._agents.keys())
List all registered agent IDs.
Returns: List of agent IDs.
223 def set_default(self, agent_id: str) -> None: 224 """ 225 Set the default agent. 226 227 Args: 228 agent_id: The agent ID to set as default. 229 230 Raises: 231 ValueError: If the agent is not registered. 232 """ 233 if agent_id not in self._agents: 234 raise ValueError(f"Agent '{agent_id}' not registered") 235 self._default_agent_id = agent_id
Set the default agent.
Args: agent_id: The agent ID to set as default.
Raises: ValueError: If the agent is not registered.
260def get_agent_registry() -> AgentRegistry: 261 """ 262 Get or create the global agent registry. 263 264 .. deprecated:: 265 Use AppContext.agent_registry instead. This function will be removed 266 in a future version. 267 268 Returns: 269 The global AgentRegistry instance. 270 """ 271 import warnings 272 273 from tiphys.context import get_context, has_context 274 275 # If AppContext is available, use it (preferred path) 276 if has_context(): 277 warnings.warn( 278 "get_agent_registry() is deprecated. Use AppContext.agent_registry instead.", 279 DeprecationWarning, 280 stacklevel=2, 281 ) 282 return get_context().agent_registry 283 284 # Fallback to global singleton for backwards compatibility 285 global _agent_registry 286 if _agent_registry is None: 287 _agent_registry = AgentRegistry() 288 return _agent_registry
Get or create the global agent registry.
Deprecated since version : Use AppContext.agent_registry instead. This function will be removed in a future version.
Returns: The global AgentRegistry instance.
291def create_default_agent( 292 agent_id: str = "default", 293 name: str = "Tiphys", 294 **kwargs: Any, 295) -> AgentDefinition: 296 """ 297 Create a default agent definition. 298 299 Args: 300 agent_id: Agent identifier. 301 name: Display name. 302 **kwargs: Additional configuration. 303 304 Returns: 305 A new AgentDefinition. 306 """ 307 return AgentDefinition( 308 id=agent_id, 309 name=name, 310 **kwargs, 311 )
Create a default agent definition.
Args: agent_id: Agent identifier. name: Display name. **kwargs: Additional configuration.
Returns: A new AgentDefinition.
18class RouteResolver: 19 """ 20 Resolves routing contexts to agents. 21 22 Evaluates bindings in priority order and returns the matching agent. 23 """ 24 25 def __init__( 26 self, 27 agent_registry: AgentRegistry | None = None, 28 default_agent_id: str | None = None, 29 ) -> None: 30 """ 31 Initialize the route resolver. 32 33 Args: 34 agent_registry: Agent registry. Uses global registry if not provided. 35 default_agent_id: Default agent ID for fallback. 36 """ 37 self.agent_registry = agent_registry or get_agent_registry() 38 self._bindings: list[AgentBinding] = [] 39 self._default_agent_id = default_agent_id 40 41 def add_binding(self, binding: AgentBinding) -> None: 42 """ 43 Add a routing binding. 44 45 Args: 46 binding: The binding to add. 47 """ 48 self._bindings.append(binding) 49 # Keep sorted by priority (highest first) 50 self._bindings.sort(key=lambda b: b.priority, reverse=True) 51 52 def remove_binding(self, binding: AgentBinding) -> bool: 53 """ 54 Remove a routing binding. 55 56 Args: 57 binding: The binding to remove. 58 59 Returns: 60 True if removed, False if not found. 61 """ 62 try: 63 self._bindings.remove(binding) 64 return True 65 except ValueError: 66 return False 67 68 def clear_bindings(self) -> None: 69 """Remove all bindings.""" 70 self._bindings.clear() 71 72 def list_bindings(self) -> list[AgentBinding]: 73 """ 74 Get all bindings sorted by priority. 75 76 Returns: 77 List of bindings. 78 """ 79 return list(self._bindings) 80 81 def resolve(self, context: RoutingContext) -> AgentDefinition | None: 82 """ 83 Resolve a routing context to an agent. 84 85 Evaluates bindings in priority order until a match is found. 86 Falls back to default agent if no binding matches. 87 88 Args: 89 context: The routing context. 90 91 Returns: 92 The resolved agent, or None if no agent found. 93 """ 94 # Try to find a matching binding 95 for binding in self._bindings: 96 if binding.matches( 97 peer_id=context.peer_id, 98 group_id=context.group_id, 99 channel_name=context.channel_name, 100 ): 101 agent = self.agent_registry.get(binding.agent_id) 102 if agent and agent.enabled: 103 logger.debug( 104 "Route resolved via binding", 105 binding_type=binding.binding_type.value, 106 agent_id=agent.id, 107 peer_id=context.peer_id, 108 group_id=context.group_id, 109 ) 110 return agent 111 112 # Fall back to default 113 default_id = self._default_agent_id or self.agent_registry.default_agent_id 114 if default_id: 115 agent = self.agent_registry.get(default_id) 116 if agent and agent.enabled: 117 logger.debug( 118 "Route resolved to default agent", 119 agent_id=agent.id, 120 peer_id=context.peer_id, 121 ) 122 return agent 123 124 # Try any enabled agent 125 for agent in self.agent_registry.list_agents(): 126 if agent.enabled: 127 logger.debug( 128 "Route resolved to first available agent", 129 agent_id=agent.id, 130 ) 131 return agent 132 133 return None 134 135 def resolve_agent_id(self, context: RoutingContext) -> str | None: 136 """ 137 Resolve a routing context to an agent ID. 138 139 Args: 140 context: The routing context. 141 142 Returns: 143 The resolved agent ID, or None. 144 """ 145 agent = self.resolve(context) 146 return agent.id if agent else None 147 148 def set_default_agent(self, agent_id: str) -> None: 149 """ 150 Set the default agent for this resolver. 151 152 Args: 153 agent_id: The agent ID. 154 """ 155 self._default_agent_id = agent_id
Resolves routing contexts to agents.
Evaluates bindings in priority order and returns the matching agent.
25 def __init__( 26 self, 27 agent_registry: AgentRegistry | None = None, 28 default_agent_id: str | None = None, 29 ) -> None: 30 """ 31 Initialize the route resolver. 32 33 Args: 34 agent_registry: Agent registry. Uses global registry if not provided. 35 default_agent_id: Default agent ID for fallback. 36 """ 37 self.agent_registry = agent_registry or get_agent_registry() 38 self._bindings: list[AgentBinding] = [] 39 self._default_agent_id = default_agent_id
Initialize the route resolver.
Args: agent_registry: Agent registry. Uses global registry if not provided. default_agent_id: Default agent ID for fallback.
41 def add_binding(self, binding: AgentBinding) -> None: 42 """ 43 Add a routing binding. 44 45 Args: 46 binding: The binding to add. 47 """ 48 self._bindings.append(binding) 49 # Keep sorted by priority (highest first) 50 self._bindings.sort(key=lambda b: b.priority, reverse=True)
Add a routing binding.
Args: binding: The binding to add.
52 def remove_binding(self, binding: AgentBinding) -> bool: 53 """ 54 Remove a routing binding. 55 56 Args: 57 binding: The binding to remove. 58 59 Returns: 60 True if removed, False if not found. 61 """ 62 try: 63 self._bindings.remove(binding) 64 return True 65 except ValueError: 66 return False
Remove a routing binding.
Args: binding: The binding to remove.
Returns: True if removed, False if not found.
72 def list_bindings(self) -> list[AgentBinding]: 73 """ 74 Get all bindings sorted by priority. 75 76 Returns: 77 List of bindings. 78 """ 79 return list(self._bindings)
Get all bindings sorted by priority.
Returns: List of bindings.
81 def resolve(self, context: RoutingContext) -> AgentDefinition | None: 82 """ 83 Resolve a routing context to an agent. 84 85 Evaluates bindings in priority order until a match is found. 86 Falls back to default agent if no binding matches. 87 88 Args: 89 context: The routing context. 90 91 Returns: 92 The resolved agent, or None if no agent found. 93 """ 94 # Try to find a matching binding 95 for binding in self._bindings: 96 if binding.matches( 97 peer_id=context.peer_id, 98 group_id=context.group_id, 99 channel_name=context.channel_name, 100 ): 101 agent = self.agent_registry.get(binding.agent_id) 102 if agent and agent.enabled: 103 logger.debug( 104 "Route resolved via binding", 105 binding_type=binding.binding_type.value, 106 agent_id=agent.id, 107 peer_id=context.peer_id, 108 group_id=context.group_id, 109 ) 110 return agent 111 112 # Fall back to default 113 default_id = self._default_agent_id or self.agent_registry.default_agent_id 114 if default_id: 115 agent = self.agent_registry.get(default_id) 116 if agent and agent.enabled: 117 logger.debug( 118 "Route resolved to default agent", 119 agent_id=agent.id, 120 peer_id=context.peer_id, 121 ) 122 return agent 123 124 # Try any enabled agent 125 for agent in self.agent_registry.list_agents(): 126 if agent.enabled: 127 logger.debug( 128 "Route resolved to first available agent", 129 agent_id=agent.id, 130 ) 131 return agent 132 133 return None
Resolve a routing context to an agent.
Evaluates bindings in priority order until a match is found. Falls back to default agent if no binding matches.
Args: context: The routing context.
Returns: The resolved agent, or None if no agent found.
135 def resolve_agent_id(self, context: RoutingContext) -> str | None: 136 """ 137 Resolve a routing context to an agent ID. 138 139 Args: 140 context: The routing context. 141 142 Returns: 143 The resolved agent ID, or None. 144 """ 145 agent = self.resolve(context) 146 return agent.id if agent else None
Resolve a routing context to an agent ID.
Args: context: The routing context.
Returns: The resolved agent ID, or None.
148 def set_default_agent(self, agent_id: str) -> None: 149 """ 150 Set the default agent for this resolver. 151 152 Args: 153 agent_id: The agent ID. 154 """ 155 self._default_agent_id = agent_id
Set the default agent for this resolver.
Args: agent_id: The agent ID.
162def get_route_resolver() -> RouteResolver: 163 """ 164 Get or create the global route resolver. 165 166 .. deprecated:: 167 Use AppContext.route_resolver instead. This function will be removed 168 in a future version. 169 170 Returns: 171 The global RouteResolver instance. 172 """ 173 import warnings 174 175 from tiphys.context import get_context, has_context 176 177 # If AppContext is available, use it (preferred path) 178 if has_context(): 179 warnings.warn( 180 "get_route_resolver() is deprecated. Use AppContext.route_resolver instead.", 181 DeprecationWarning, 182 stacklevel=2, 183 ) 184 return get_context().route_resolver # type: ignore[return-value] 185 186 # Fallback to global singleton for backwards compatibility 187 global _route_resolver 188 if _route_resolver is None: 189 _route_resolver = RouteResolver() 190 return _route_resolver
Get or create the global route resolver.
Deprecated since version : Use AppContext.route_resolver instead. This function will be removed in a future version.
Returns: The global RouteResolver instance.
193def resolve_agent( 194 peer_id: str | None = None, 195 group_id: str | None = None, 196 channel_name: str | None = None, 197 **metadata: Any, 198) -> AgentDefinition | None: 199 """ 200 Convenience function to resolve an agent from context. 201 202 Args: 203 peer_id: The peer ID. 204 group_id: The group ID. 205 channel_name: The channel name. 206 **metadata: Extra context metadata. 207 208 Returns: 209 The resolved agent, or None. 210 """ 211 context = RoutingContext( 212 peer_id=peer_id, 213 group_id=group_id, 214 channel_name=channel_name, 215 metadata=metadata, 216 ) 217 return get_route_resolver().resolve(context)
Convenience function to resolve an agent from context.
Args: peer_id: The peer ID. group_id: The group ID. channel_name: The channel name. **metadata: Extra context metadata.
Returns: The resolved agent, or None.