hackagent 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hackagent/__init__.py +23 -0
- hackagent/agent.py +193 -0
- hackagent/api/__init__.py +1 -0
- hackagent/api/agent/__init__.py +1 -0
- hackagent/api/agent/agent_create.py +340 -0
- hackagent/api/agent/agent_destroy.py +136 -0
- hackagent/api/agent/agent_list.py +234 -0
- hackagent/api/agent/agent_partial_update.py +354 -0
- hackagent/api/agent/agent_retrieve.py +227 -0
- hackagent/api/agent/agent_update.py +354 -0
- hackagent/api/attack/__init__.py +1 -0
- hackagent/api/attack/attack_create.py +264 -0
- hackagent/api/attack/attack_destroy.py +140 -0
- hackagent/api/attack/attack_list.py +242 -0
- hackagent/api/attack/attack_partial_update.py +278 -0
- hackagent/api/attack/attack_retrieve.py +235 -0
- hackagent/api/attack/attack_update.py +278 -0
- hackagent/api/key/__init__.py +1 -0
- hackagent/api/key/key_create.py +168 -0
- hackagent/api/key/key_destroy.py +97 -0
- hackagent/api/key/key_list.py +158 -0
- hackagent/api/key/key_retrieve.py +150 -0
- hackagent/api/prompt/__init__.py +1 -0
- hackagent/api/prompt/prompt_create.py +160 -0
- hackagent/api/prompt/prompt_destroy.py +98 -0
- hackagent/api/prompt/prompt_list.py +173 -0
- hackagent/api/prompt/prompt_partial_update.py +174 -0
- hackagent/api/prompt/prompt_retrieve.py +151 -0
- hackagent/api/prompt/prompt_update.py +174 -0
- hackagent/api/result/__init__.py +1 -0
- hackagent/api/result/result_create.py +160 -0
- hackagent/api/result/result_destroy.py +98 -0
- hackagent/api/result/result_list.py +233 -0
- hackagent/api/result/result_partial_update.py +178 -0
- hackagent/api/result/result_retrieve.py +151 -0
- hackagent/api/result/result_trace_create.py +178 -0
- hackagent/api/result/result_update.py +174 -0
- hackagent/api/run/__init__.py +1 -0
- hackagent/api/run/run_create.py +172 -0
- hackagent/api/run/run_destroy.py +104 -0
- hackagent/api/run/run_list.py +260 -0
- hackagent/api/run/run_partial_update.py +186 -0
- hackagent/api/run/run_result_create.py +178 -0
- hackagent/api/run/run_retrieve.py +163 -0
- hackagent/api/run/run_run_tests_create.py +172 -0
- hackagent/api/run/run_update.py +186 -0
- hackagent/attacks/AdvPrefix/README.md +7 -0
- hackagent/attacks/AdvPrefix/__init__.py +0 -0
- hackagent/attacks/AdvPrefix/completer.py +438 -0
- hackagent/attacks/AdvPrefix/config.py +59 -0
- hackagent/attacks/AdvPrefix/preprocessing.py +521 -0
- hackagent/attacks/AdvPrefix/scorer.py +259 -0
- hackagent/attacks/AdvPrefix/scorer_parser.py +498 -0
- hackagent/attacks/AdvPrefix/selector.py +246 -0
- hackagent/attacks/AdvPrefix/step1_generate.py +324 -0
- hackagent/attacks/AdvPrefix/step4_compute_ce.py +293 -0
- hackagent/attacks/AdvPrefix/step6_get_completions.py +387 -0
- hackagent/attacks/AdvPrefix/step7_evaluate_responses.py +289 -0
- hackagent/attacks/AdvPrefix/step8_aggregate_evaluations.py +177 -0
- hackagent/attacks/AdvPrefix/step9_select_prefixes.py +59 -0
- hackagent/attacks/AdvPrefix/utils.py +192 -0
- hackagent/attacks/__init__.py +6 -0
- hackagent/attacks/advprefix.py +1136 -0
- hackagent/attacks/base.py +50 -0
- hackagent/attacks/strategies.py +539 -0
- hackagent/branding.py +143 -0
- hackagent/client.py +328 -0
- hackagent/errors.py +31 -0
- hackagent/logger.py +67 -0
- hackagent/models/__init__.py +71 -0
- hackagent/models/agent.py +240 -0
- hackagent/models/agent_request.py +169 -0
- hackagent/models/agent_type_enum.py +12 -0
- hackagent/models/attack.py +154 -0
- hackagent/models/attack_request.py +82 -0
- hackagent/models/evaluation_status_enum.py +14 -0
- hackagent/models/organization_minimal.py +68 -0
- hackagent/models/paginated_agent_list.py +123 -0
- hackagent/models/paginated_attack_list.py +123 -0
- hackagent/models/paginated_prompt_list.py +123 -0
- hackagent/models/paginated_result_list.py +123 -0
- hackagent/models/paginated_run_list.py +123 -0
- hackagent/models/paginated_user_api_key_list.py +123 -0
- hackagent/models/patched_agent_request.py +176 -0
- hackagent/models/patched_attack_request.py +92 -0
- hackagent/models/patched_prompt_request.py +162 -0
- hackagent/models/patched_result_request.py +237 -0
- hackagent/models/patched_run_request.py +138 -0
- hackagent/models/prompt.py +226 -0
- hackagent/models/prompt_request.py +155 -0
- hackagent/models/result.py +294 -0
- hackagent/models/result_list_evaluation_status.py +14 -0
- hackagent/models/result_request.py +232 -0
- hackagent/models/run.py +233 -0
- hackagent/models/run_list_status.py +12 -0
- hackagent/models/run_request.py +133 -0
- hackagent/models/status_enum.py +12 -0
- hackagent/models/step_type_enum.py +14 -0
- hackagent/models/trace.py +121 -0
- hackagent/models/trace_request.py +94 -0
- hackagent/models/user_api_key.py +201 -0
- hackagent/models/user_api_key_request.py +73 -0
- hackagent/models/user_profile_minimal.py +76 -0
- hackagent/py.typed +1 -0
- hackagent/router/__init__.py +11 -0
- hackagent/router/adapters/__init__.py +5 -0
- hackagent/router/adapters/google_adk.py +658 -0
- hackagent/router/adapters/litellm_adapter.py +290 -0
- hackagent/router/base.py +48 -0
- hackagent/router/router.py +753 -0
- hackagent/types.py +46 -0
- hackagent/utils.py +61 -0
- hackagent/vulnerabilities/__init__.py +0 -0
- hackagent-0.1.0.dist-info/LICENSE +202 -0
- hackagent-0.1.0.dist-info/METADATA +173 -0
- hackagent-0.1.0.dist-info/RECORD +117 -0
- hackagent-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Type, Optional, Union
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from hackagent.router.base import Agent
|
|
6
|
+
from hackagent.router.adapters import ADKAgentAdapter
|
|
7
|
+
from hackagent.router.adapters.litellm_adapter import LiteLLMAgentAdapter
|
|
8
|
+
from hackagent.client import AuthenticatedClient
|
|
9
|
+
from hackagent.models import (
|
|
10
|
+
AgentTypeEnum,
|
|
11
|
+
Agent as BackendAgentModel,
|
|
12
|
+
AgentRequest,
|
|
13
|
+
PatchedAgentRequest,
|
|
14
|
+
UserAPIKey,
|
|
15
|
+
)
|
|
16
|
+
from ..types import Unset, UNSET
|
|
17
|
+
from hackagent.api.agent import agent_list, agent_create, agent_partial_update
|
|
18
|
+
from hackagent.api.key import key_list
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# --- Agent Type to Adapter Mapping ---
|
|
23
|
+
AGENT_TYPE_TO_ADAPTER_MAP: Dict[AgentTypeEnum, Type[Agent]] = {
|
|
24
|
+
AgentTypeEnum.GOOGLE_ADK: ADKAgentAdapter,
|
|
25
|
+
AgentTypeEnum.LITELMM: LiteLLMAgentAdapter,
|
|
26
|
+
# AgentTypeEnum.OPENAI: OpenAIAgentAdapter, # Example for future
|
|
27
|
+
# Add other agent types and their corresponding adapters here
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentRouter:
|
|
32
|
+
"""
|
|
33
|
+
Manages a single agent's configuration and routes requests to its adapter.
|
|
34
|
+
|
|
35
|
+
The router is initialized with the details of an agent, registers it with the
|
|
36
|
+
backend (if not already present or if metadata needs an update), and instantiates
|
|
37
|
+
the appropriate adapter. It then uses this adapter for request routing.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def _fetch_organization_id(self) -> UUID:
|
|
41
|
+
"""Fetches and returns the organization ID (UUID) associated with the API key.
|
|
42
|
+
Raises RuntimeError if not found or if the organization attribute is not a UUID.
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
logger.debug(
|
|
46
|
+
"AgentRouter: Attempting to retrieve Organization ID by listing API keys..."
|
|
47
|
+
)
|
|
48
|
+
keys_response = key_list.sync_detailed(client=self.client)
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
keys_response.status_code == 200
|
|
52
|
+
and keys_response.parsed
|
|
53
|
+
and keys_response.parsed.results
|
|
54
|
+
):
|
|
55
|
+
current_token = self.client.token
|
|
56
|
+
key_results: list[UserAPIKey] = keys_response.parsed.results
|
|
57
|
+
for key_obj in key_results:
|
|
58
|
+
if current_token.startswith(key_obj.prefix):
|
|
59
|
+
if hasattr(key_obj, "organization") and isinstance(
|
|
60
|
+
key_obj.organization, UUID
|
|
61
|
+
):
|
|
62
|
+
logger.info(
|
|
63
|
+
f"AgentRouter: Successfully determined Organization ID: {key_obj.organization} from key prefix '{key_obj.prefix}'."
|
|
64
|
+
)
|
|
65
|
+
return key_obj.organization
|
|
66
|
+
else:
|
|
67
|
+
org_type = (
|
|
68
|
+
type(key_obj.organization).__name__
|
|
69
|
+
if hasattr(key_obj, "organization")
|
|
70
|
+
else "Missing"
|
|
71
|
+
)
|
|
72
|
+
logger.warning(
|
|
73
|
+
f"AgentRouter: Key prefix '{key_obj.prefix}' matched, but 'organization' is not UUID (type: {org_type}). Skipping for Org ID."
|
|
74
|
+
)
|
|
75
|
+
logger.error(
|
|
76
|
+
f"AgentRouter: No API key found with a valid Organization (UUID) for token prefix '{current_token[:8]}...'."
|
|
77
|
+
)
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
"AgentRouter: Could not determine Organization ID (UUID) from API keys."
|
|
80
|
+
)
|
|
81
|
+
elif keys_response.parsed and not keys_response.parsed.results:
|
|
82
|
+
logger.error(
|
|
83
|
+
"AgentRouter: API key list empty. Cannot find Organization ID."
|
|
84
|
+
)
|
|
85
|
+
raise RuntimeError(
|
|
86
|
+
"AgentRouter: API key list empty for Organization ID retrieval."
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
content = (
|
|
90
|
+
keys_response.content.decode() if keys_response.content else "N/A"
|
|
91
|
+
)
|
|
92
|
+
logger.error(
|
|
93
|
+
f"AgentRouter: Failed to list keys for Org ID. Status: {keys_response.status_code}, Body: {content}"
|
|
94
|
+
)
|
|
95
|
+
raise RuntimeError(
|
|
96
|
+
f"AgentRouter: API key list failed for Organization ID (status {keys_response.status_code})."
|
|
97
|
+
)
|
|
98
|
+
except RuntimeError:
|
|
99
|
+
raise
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error(
|
|
102
|
+
f"AgentRouter: Exception fetching Organization ID: {e}", exc_info=True
|
|
103
|
+
)
|
|
104
|
+
raise RuntimeError(f"AgentRouter: Exception fetching Organization ID: {e}")
|
|
105
|
+
|
|
106
|
+
def _fetch_user_id_str(self) -> str:
|
|
107
|
+
"""Fetches and returns the user ID (as a string from UserAPIKey.user)
|
|
108
|
+
associated with the API key. Raises RuntimeError if not found or user attribute is not an int.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
logger.debug(
|
|
112
|
+
"AgentRouter: Attempting to retrieve User ID by listing API keys..."
|
|
113
|
+
)
|
|
114
|
+
keys_response = key_list.sync_detailed(client=self.client)
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
keys_response.status_code == 200
|
|
118
|
+
and keys_response.parsed
|
|
119
|
+
and keys_response.parsed.results
|
|
120
|
+
):
|
|
121
|
+
current_token = self.client.token
|
|
122
|
+
key_results: list[UserAPIKey] = keys_response.parsed.results
|
|
123
|
+
for key_obj in key_results:
|
|
124
|
+
if current_token.startswith(key_obj.prefix):
|
|
125
|
+
if hasattr(key_obj, "user") and isinstance(key_obj.user, int):
|
|
126
|
+
user_id_as_str = str(key_obj.user)
|
|
127
|
+
logger.info(
|
|
128
|
+
f"AgentRouter: Successfully determined User ID (str): {user_id_as_str} from key prefix '{key_obj.prefix}'."
|
|
129
|
+
)
|
|
130
|
+
return user_id_as_str
|
|
131
|
+
else:
|
|
132
|
+
user_type = (
|
|
133
|
+
type(key_obj.user).__name__
|
|
134
|
+
if hasattr(key_obj, "user")
|
|
135
|
+
else "Missing"
|
|
136
|
+
)
|
|
137
|
+
logger.warning(
|
|
138
|
+
f"AgentRouter: Key prefix '{key_obj.prefix}' matched, but 'user' is not int (type: {user_type}). Skipping for User ID."
|
|
139
|
+
)
|
|
140
|
+
logger.error(
|
|
141
|
+
f"AgentRouter: No API key found with a valid User (int) for token prefix '{current_token[:8]}...'."
|
|
142
|
+
)
|
|
143
|
+
raise RuntimeError(
|
|
144
|
+
"AgentRouter: Could not determine User ID (int) from API keys."
|
|
145
|
+
)
|
|
146
|
+
elif keys_response.parsed and not keys_response.parsed.results:
|
|
147
|
+
logger.error("AgentRouter: API key list empty. Cannot find User ID.")
|
|
148
|
+
raise RuntimeError(
|
|
149
|
+
"AgentRouter: API key list empty for User ID retrieval."
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
content = (
|
|
153
|
+
keys_response.content.decode() if keys_response.content else "N/A"
|
|
154
|
+
)
|
|
155
|
+
logger.error(
|
|
156
|
+
f"AgentRouter: Failed to list keys for User ID. Status: {keys_response.status_code}, Body: {content}"
|
|
157
|
+
)
|
|
158
|
+
raise RuntimeError(
|
|
159
|
+
f"AgentRouter: API key list failed for User ID (status {keys_response.status_code})."
|
|
160
|
+
)
|
|
161
|
+
except RuntimeError:
|
|
162
|
+
raise
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"AgentRouter: Exception fetching User ID: {e}", exc_info=True)
|
|
165
|
+
raise RuntimeError(f"AgentRouter: Exception fetching User ID: {e}")
|
|
166
|
+
|
|
167
|
+
def __init__(
|
|
168
|
+
self,
|
|
169
|
+
client: AuthenticatedClient,
|
|
170
|
+
name: str,
|
|
171
|
+
agent_type: AgentTypeEnum,
|
|
172
|
+
endpoint: str,
|
|
173
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
174
|
+
adapter_operational_config: Optional[Dict[str, Any]] = None,
|
|
175
|
+
overwrite_metadata: bool = True, # Controls if backend agent metadata is updated if agent exists
|
|
176
|
+
):
|
|
177
|
+
"""
|
|
178
|
+
Initializes the AgentRouter and registers a single agent.
|
|
179
|
+
|
|
180
|
+
Ensures the specified agent exists in the backend (creating or updating as needed),
|
|
181
|
+
then instantiates and stores its adapter in the router's registry.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
client: Authenticated client for backend API interaction.
|
|
185
|
+
name: Name for the agent in the backend.
|
|
186
|
+
agent_type: The AgentTypeEnum for the agent (e.g., AgentTypeEnum.GOOGLE_ADK).
|
|
187
|
+
endpoint: API endpoint URL for the agent service itself (used for backend registration
|
|
188
|
+
and potentially by the adapter if not overridden by backend_agent.endpoint).
|
|
189
|
+
metadata: Metadata for the backend agent record.
|
|
190
|
+
For ADK, adk_app_name is no longer explicitly managed here if it's same as agent name.
|
|
191
|
+
For LiteLLM, SHOULD include {'name': 'model_name',
|
|
192
|
+
'endpoint': 'endpoint',
|
|
193
|
+
'api_key': 'optional_env_var_for_api_key', ...}
|
|
194
|
+
adapter_operational_config: Runtime config for the adapter instance.
|
|
195
|
+
Overrides or augments values from backend_agent.metadata.
|
|
196
|
+
For ADK, may include {'user_id': ..., 'session_id': ...}.
|
|
197
|
+
For LiteLLM, MUST provide 'name' (model string) if not in backend metadata.
|
|
198
|
+
overwrite_metadata: If True, and an agent exists, its backend metadata is updated.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
ValueError: If agent_type is unsupported or adapter instantiation fails.
|
|
202
|
+
RuntimeError: If backend communication or agent processing fails.
|
|
203
|
+
"""
|
|
204
|
+
self.client = client
|
|
205
|
+
self._agent_registry: Dict[str, Agent] = {}
|
|
206
|
+
|
|
207
|
+
self.organization_id = self._fetch_organization_id()
|
|
208
|
+
self.user_id_str = self._fetch_user_id_str()
|
|
209
|
+
logger.info(
|
|
210
|
+
f"AgentRouter initialized with Organization ID (UUID): {self.organization_id} and User ID (str): {self.user_id_str}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if agent_type not in AGENT_TYPE_TO_ADAPTER_MAP:
|
|
214
|
+
raise ValueError(
|
|
215
|
+
f"Unsupported agent type: {agent_type}. "
|
|
216
|
+
f"Supported types: {list(AGENT_TYPE_TO_ADAPTER_MAP.keys())}"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
actual_metadata = metadata.copy() if metadata is not None else {}
|
|
220
|
+
|
|
221
|
+
# adapter_operational_config is passed in, merge with any defaults we set here
|
|
222
|
+
current_adapter_op_config = (
|
|
223
|
+
adapter_operational_config.copy() if adapter_operational_config else {}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if agent_type == AgentTypeEnum.GOOGLE_ADK:
|
|
227
|
+
# Ensure user_id is in the op_config for ADK, using the one fetched from API key
|
|
228
|
+
if "user_id" not in current_adapter_op_config:
|
|
229
|
+
current_adapter_op_config["user_id"] = self.user_id_str
|
|
230
|
+
logger.info(
|
|
231
|
+
f"ADK Agent: Using fetched User ID '{self.user_id_str}' for adapter operational config."
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
logger.warning(
|
|
235
|
+
f"ADK Agent: 'user_id' was already present in adapter_operational_config ('{current_adapter_op_config['user_id']}'). Using that value instead of fetched one."
|
|
236
|
+
)
|
|
237
|
+
# session_id will be handled later, as it depends on run_id
|
|
238
|
+
|
|
239
|
+
self.backend_agent = self.ensure_agent_in_backend(
|
|
240
|
+
name=name,
|
|
241
|
+
agent_type=agent_type,
|
|
242
|
+
endpoint_for_backend=endpoint,
|
|
243
|
+
metadata_for_backend=actual_metadata,
|
|
244
|
+
update_metadata_if_exists=overwrite_metadata,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
registration_key = str(self.backend_agent.id)
|
|
248
|
+
|
|
249
|
+
self._configure_and_instantiate_adapter(
|
|
250
|
+
name=name,
|
|
251
|
+
agent_type=agent_type,
|
|
252
|
+
registration_key=registration_key,
|
|
253
|
+
adapter_operational_config=current_adapter_op_config,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def _configure_and_instantiate_adapter(
|
|
257
|
+
self,
|
|
258
|
+
name: str,
|
|
259
|
+
agent_type: AgentTypeEnum,
|
|
260
|
+
registration_key: str,
|
|
261
|
+
adapter_operational_config: Optional[Dict[str, Any]],
|
|
262
|
+
) -> None:
|
|
263
|
+
"""
|
|
264
|
+
Configures and instantiates the appropriate agent adapter based on agent_type
|
|
265
|
+
and stores it in the router's registry.
|
|
266
|
+
"""
|
|
267
|
+
adapter_class = AGENT_TYPE_TO_ADAPTER_MAP[
|
|
268
|
+
agent_type
|
|
269
|
+
] # agent_type already validated in __init__
|
|
270
|
+
|
|
271
|
+
# Start with the operational config passed in
|
|
272
|
+
adapter_instance_config = (
|
|
273
|
+
adapter_operational_config.copy() if adapter_operational_config else {}
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Type-specific adapter configuration
|
|
277
|
+
if agent_type == AgentTypeEnum.GOOGLE_ADK:
|
|
278
|
+
adapter_instance_config["name"] = self.backend_agent.name
|
|
279
|
+
adapter_instance_config["endpoint"] = self.backend_agent.endpoint
|
|
280
|
+
if "user_id" not in adapter_instance_config:
|
|
281
|
+
logger.error(
|
|
282
|
+
f"CRITICAL: user_id not found in adapter_instance_config for ADK agent '{self.backend_agent.name}' just before adapter instantiation. This should have been set in __init__."
|
|
283
|
+
)
|
|
284
|
+
# Fallback, though this indicates a logic flaw if reached.
|
|
285
|
+
adapter_instance_config["user_id"] = self.user_id_str
|
|
286
|
+
|
|
287
|
+
elif agent_type == AgentTypeEnum.LITELMM:
|
|
288
|
+
if (
|
|
289
|
+
"name" not in adapter_instance_config
|
|
290
|
+
): # 'name' is the model string for LiteLLM
|
|
291
|
+
if (
|
|
292
|
+
isinstance(self.backend_agent.metadata, dict)
|
|
293
|
+
and "name" in self.backend_agent.metadata
|
|
294
|
+
):
|
|
295
|
+
adapter_instance_config["name"] = self.backend_agent.metadata[
|
|
296
|
+
"name"
|
|
297
|
+
]
|
|
298
|
+
else:
|
|
299
|
+
raise ValueError(
|
|
300
|
+
f"LiteLLM agent '{name}' (ID: {registration_key}) missing "
|
|
301
|
+
f"'name' (model string) in adapter_operational_config or backend metadata. "
|
|
302
|
+
f"Cannot configure LiteLLMAgentAdapter."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Copy other relevant LiteLLM settings from backend_agent.metadata if not already in adapter_instance_config
|
|
306
|
+
optional_litellm_keys = [
|
|
307
|
+
"endpoint",
|
|
308
|
+
"api_key",
|
|
309
|
+
"max_new_tokens",
|
|
310
|
+
"temperature",
|
|
311
|
+
"top_p",
|
|
312
|
+
]
|
|
313
|
+
if isinstance(self.backend_agent.metadata, dict):
|
|
314
|
+
for key in optional_litellm_keys:
|
|
315
|
+
if (
|
|
316
|
+
key not in adapter_instance_config
|
|
317
|
+
and key in self.backend_agent.metadata
|
|
318
|
+
):
|
|
319
|
+
adapter_instance_config[key] = self.backend_agent.metadata[key]
|
|
320
|
+
|
|
321
|
+
# Instantiate and register the adapter
|
|
322
|
+
try:
|
|
323
|
+
adapter_instance = adapter_class(
|
|
324
|
+
id=registration_key, config=adapter_instance_config
|
|
325
|
+
)
|
|
326
|
+
self._agent_registry[registration_key] = adapter_instance
|
|
327
|
+
logger.info(
|
|
328
|
+
f"Agent '{name}' (Backend ID: {registration_key}, Type: {agent_type.value}) "
|
|
329
|
+
f"successfully initialized and registered with adapter {adapter_class.__name__}. "
|
|
330
|
+
f"Adapter config keys: {list(adapter_instance_config.keys())}" # Log keys for debug
|
|
331
|
+
)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.error(
|
|
334
|
+
f"Failed to instantiate adapter for agent '{name}' "
|
|
335
|
+
f"(Backend ID: {registration_key}): {e}",
|
|
336
|
+
exc_info=True,
|
|
337
|
+
)
|
|
338
|
+
raise ValueError(
|
|
339
|
+
f"Failed to instantiate adapter {adapter_class.__name__}: {e}"
|
|
340
|
+
) from e
|
|
341
|
+
|
|
342
|
+
def _find_existing_agent(
|
|
343
|
+
self,
|
|
344
|
+
name: str,
|
|
345
|
+
agent_type: AgentTypeEnum,
|
|
346
|
+
) -> Optional[BackendAgentModel]:
|
|
347
|
+
"""
|
|
348
|
+
Finds an existing agent by name, type, and organization in the backend.
|
|
349
|
+
Uses self.organization_id (UUID) for matching.
|
|
350
|
+
"""
|
|
351
|
+
logger.debug(
|
|
352
|
+
f"SYNC_DEBUG: Entered _find_existing_agent for Name='{name}', Type='{agent_type.value}', OrgID='{self.organization_id}' (UUID)"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
current_page: Union[Unset, int] = UNSET
|
|
356
|
+
agents_processed_count = 0
|
|
357
|
+
|
|
358
|
+
while True:
|
|
359
|
+
list_response = None
|
|
360
|
+
try:
|
|
361
|
+
list_response = agent_list.sync_detailed(
|
|
362
|
+
client=self.client, page=current_page
|
|
363
|
+
)
|
|
364
|
+
logger.debug(
|
|
365
|
+
f"SYNC_DEBUG: Fetched page of agents. Status: {list_response.status_code if list_response else 'N/A'}"
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
logger.error(
|
|
370
|
+
f"SYNC_DEBUG: An unexpected error occurred during 'agents_list.sync_detailed' while fetching page {current_page if not isinstance(current_page, Unset) else 'initial'}: {e}",
|
|
371
|
+
exc_info=True,
|
|
372
|
+
)
|
|
373
|
+
return None # Or handle error more gracefully
|
|
374
|
+
|
|
375
|
+
if (
|
|
376
|
+
list_response
|
|
377
|
+
and list_response.status_code == 200
|
|
378
|
+
and list_response.parsed
|
|
379
|
+
):
|
|
380
|
+
paginated_result = list_response.parsed
|
|
381
|
+
results_list = getattr(paginated_result, "results", [])
|
|
382
|
+
logger.debug(
|
|
383
|
+
f"SYNC_DEBUG: Page {current_page if not isinstance(current_page, Unset) else 'initial'} - Number of results on page: {len(results_list) if results_list else 0}"
|
|
384
|
+
)
|
|
385
|
+
if not isinstance(results_list, list):
|
|
386
|
+
logger.warning(
|
|
387
|
+
f"SYNC_DEBUG: Expected 'results' to be a list, but got {type(results_list)}. Full parsed response: {paginated_result}"
|
|
388
|
+
)
|
|
389
|
+
results_list = []
|
|
390
|
+
|
|
391
|
+
for agent_model in results_list:
|
|
392
|
+
agents_processed_count += 1
|
|
393
|
+
logger.debug(
|
|
394
|
+
f"SYNC_DEBUG: Checking agent: ID={agent_model.id}, Name={getattr(agent_model, 'name', 'N/A')}, Type={getattr(agent_model, 'agent_type', getattr(agent_model, 'type', 'N/A'))}, Org={getattr(agent_model, 'organization', 'N/A')}"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
name_matches = (
|
|
398
|
+
hasattr(agent_model, "name") and agent_model.name == name
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
org_matches = False
|
|
402
|
+
# agent_model.organization is UUID as per hackagent.models.Agent
|
|
403
|
+
if hasattr(agent_model, "organization") and isinstance(
|
|
404
|
+
agent_model.organization, UUID
|
|
405
|
+
):
|
|
406
|
+
if agent_model.organization == self.organization_id:
|
|
407
|
+
org_matches = True
|
|
408
|
+
# else: # No need for else here, org_matches remains false
|
|
409
|
+
# logger.debug(f"SYNC_DEBUG: OrgID (UUID) mismatch: agent_model.organization ('{agent_model.organization}') != expected self.organization_id ('{self.organization_id}') for agent '{agent_model.name}'")
|
|
410
|
+
# Check organization_detail.id as a fallback, though agent_model.organization should be primary
|
|
411
|
+
elif (
|
|
412
|
+
hasattr(agent_model, "organization_detail")
|
|
413
|
+
and hasattr(agent_model.organization_detail, "id")
|
|
414
|
+
and isinstance(agent_model.organization_detail.id, UUID)
|
|
415
|
+
):
|
|
416
|
+
if agent_model.organization_detail.id == self.organization_id:
|
|
417
|
+
org_matches = True
|
|
418
|
+
logger.debug(
|
|
419
|
+
f"SYNC_DEBUG: Matched OrgID via organization_detail.id for agent '{agent_model.name}'"
|
|
420
|
+
)
|
|
421
|
+
# else:
|
|
422
|
+
# logger.debug(f"SYNC_DEBUG: OrgID (UUID) mismatch via organization_detail.id: ('{agent_model.organization_detail.id}') != expected self.organization_id ('{self.organization_id}') for agent '{agent_model.name}'")
|
|
423
|
+
# The case where agent_model.organization is an int should not happen if model is correct, but good to log if it does.
|
|
424
|
+
elif hasattr(agent_model, "organization") and isinstance(
|
|
425
|
+
agent_model.organization, int
|
|
426
|
+
):
|
|
427
|
+
logger.warning(
|
|
428
|
+
f"SYNC_DEBUG: agent_model.organization is an int ('{agent_model.organization}') for agent '{agent_model.name}'. Schema mismatch with expected UUID ('{self.organization_id}')."
|
|
429
|
+
)
|
|
430
|
+
# else: # Log if no organization attribute could be reliably checked
|
|
431
|
+
# logger.debug(f"SYNC_DEBUG: Could not determine organization ID for comparison for agent '{agent_model.name}'. Expected UUID: {self.organization_id}")
|
|
432
|
+
|
|
433
|
+
type_matches = False
|
|
434
|
+
# The `agent_model` from the list might have `agent_type` (as per model def) or just `type`.
|
|
435
|
+
# The `type` attribute from `BackendAgentModel` (aliased as `Agent`) is `agent_type` in its definition.
|
|
436
|
+
# `AgentTypeEnum` is what `agent_type` (parameter) is.
|
|
437
|
+
current_agent_type_val = None
|
|
438
|
+
if (
|
|
439
|
+
hasattr(agent_model, "agent_type")
|
|
440
|
+
and agent_model.agent_type is not None
|
|
441
|
+
and not isinstance(agent_model.agent_type, Unset)
|
|
442
|
+
):
|
|
443
|
+
if isinstance(agent_model.agent_type, AgentTypeEnum):
|
|
444
|
+
current_agent_type_val = agent_model.agent_type.value
|
|
445
|
+
elif isinstance(
|
|
446
|
+
agent_model.agent_type, str
|
|
447
|
+
): # If it's already a string
|
|
448
|
+
current_agent_type_val = agent_model.agent_type
|
|
449
|
+
elif (
|
|
450
|
+
hasattr(agent_model, "type") and agent_model.type is not None
|
|
451
|
+
): # Fallback for older/different field name
|
|
452
|
+
if isinstance(agent_model.type, AgentTypeEnum):
|
|
453
|
+
current_agent_type_val = agent_model.type.value
|
|
454
|
+
elif isinstance(agent_model.type, str):
|
|
455
|
+
current_agent_type_val = agent_model.type
|
|
456
|
+
|
|
457
|
+
if (
|
|
458
|
+
current_agent_type_val is not None
|
|
459
|
+
and current_agent_type_val == agent_type.value
|
|
460
|
+
):
|
|
461
|
+
type_matches = True
|
|
462
|
+
|
|
463
|
+
if not name_matches:
|
|
464
|
+
logger.debug(
|
|
465
|
+
f"SYNC_DEBUG: Agent ID '{agent_model.id}' ('{agent_model.name}') failed name match (expected '{name}')"
|
|
466
|
+
)
|
|
467
|
+
elif not org_matches:
|
|
468
|
+
logger.debug(
|
|
469
|
+
f"SYNC_DEBUG: Agent ID '{agent_model.id}' ('{agent_model.name}') failed organization match (expected UUID '{self.organization_id}', found '{getattr(agent_model, 'organization', 'N/A')}' or detail '{getattr(agent_model.organization_detail, 'id', 'N/A') if hasattr(agent_model, 'organization_detail') else 'N/A'}')"
|
|
470
|
+
)
|
|
471
|
+
elif not type_matches:
|
|
472
|
+
logger.debug(
|
|
473
|
+
f"SYNC_DEBUG: Agent ID '{agent_model.id}' ('{agent_model.name}') failed type match (expected '{agent_type.value}', found '{current_agent_type_val}')"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
if name_matches and org_matches and type_matches:
|
|
477
|
+
logger.info(
|
|
478
|
+
f"SYNC_DEBUG: Found existing backend agent '{name}' (Type: {agent_type.value}, OrgID: {self.organization_id}) "
|
|
479
|
+
f"with ID {agent_model.id} on page {current_page if not isinstance(current_page, Unset) else 'initial'}. Processed {agents_processed_count} agents total so far."
|
|
480
|
+
)
|
|
481
|
+
return agent_model
|
|
482
|
+
|
|
483
|
+
if (
|
|
484
|
+
hasattr(paginated_result, "next_")
|
|
485
|
+
and paginated_result.next_
|
|
486
|
+
and not isinstance(paginated_result.next_, Unset)
|
|
487
|
+
):
|
|
488
|
+
next_page_url = paginated_result.next_
|
|
489
|
+
# Extract page number if it's a full URL. This is a bit simplistic.
|
|
490
|
+
# A more robust way would be to parse URL params if the API returns full URLs for next.
|
|
491
|
+
# If the API just returns the next page number, this is simpler.
|
|
492
|
+
# Assuming API might return simple page numbers or full URLs with ?page=NUMBER
|
|
493
|
+
try:
|
|
494
|
+
if isinstance(next_page_url, str) and "page=" in next_page_url:
|
|
495
|
+
current_page = int(
|
|
496
|
+
next_page_url.split("page=")[-1].split("&")[0]
|
|
497
|
+
)
|
|
498
|
+
elif isinstance(
|
|
499
|
+
next_page_url, int
|
|
500
|
+
): # If API directly gives next page number
|
|
501
|
+
current_page = next_page_url
|
|
502
|
+
else: # Fallback for simple increment if only a URL string is given without obvious page number
|
|
503
|
+
current_page = (
|
|
504
|
+
current_page if isinstance(current_page, int) else 1
|
|
505
|
+
) + 1
|
|
506
|
+
except ValueError:
|
|
507
|
+
logger.warning(
|
|
508
|
+
f"Could not parse next page number from URL: {next_page_url}. Using simple increment."
|
|
509
|
+
)
|
|
510
|
+
current_page = (
|
|
511
|
+
current_page if isinstance(current_page, int) else 1
|
|
512
|
+
) + 1
|
|
513
|
+
|
|
514
|
+
logger.debug(
|
|
515
|
+
f"SYNC_DEBUG: Moving to next page of agents: {current_page}"
|
|
516
|
+
)
|
|
517
|
+
else:
|
|
518
|
+
logger.debug(
|
|
519
|
+
f"SYNC_DEBUG: No more pages of agents to fetch. Processed {agents_processed_count} agents in total."
|
|
520
|
+
)
|
|
521
|
+
break
|
|
522
|
+
|
|
523
|
+
elif list_response and list_response.status_code != 200:
|
|
524
|
+
logger.error(
|
|
525
|
+
f"SYNC_DEBUG: Failed to list agents on page {current_page if not isinstance(current_page, Unset) else 'initial'}. Status: {list_response.status_code}, "
|
|
526
|
+
f"Body: {list_response.content.decode() if list_response.content else 'N/A'}"
|
|
527
|
+
)
|
|
528
|
+
return None
|
|
529
|
+
elif not list_response:
|
|
530
|
+
logger.error(
|
|
531
|
+
f"SYNC_DEBUG: Failed to get any response from agents_list API for page {current_page if not isinstance(current_page, Unset) else 'initial'}."
|
|
532
|
+
)
|
|
533
|
+
return None
|
|
534
|
+
else:
|
|
535
|
+
logger.warning(
|
|
536
|
+
f"SYNC_DEBUG: Unexpected state after trying to fetch page {current_page if not isinstance(current_page, Unset) else 'initial'}. list_response: {list_response}"
|
|
537
|
+
)
|
|
538
|
+
return None
|
|
539
|
+
|
|
540
|
+
logger.debug(
|
|
541
|
+
f"SYNC_DEBUG: No existing backend agent found matching Name='{name}', Type='{agent_type.value}', OrgID='{self.organization_id}' after searching all pages."
|
|
542
|
+
)
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
def _update_agent_metadata(
|
|
546
|
+
self, agent_id: UUID, metadata_to_update: Dict[str, Any]
|
|
547
|
+
) -> BackendAgentModel:
|
|
548
|
+
"""Updates the metadata of an existing backend agent."""
|
|
549
|
+
logger.info(f"Attempting to update metadata for backend agent ID: {agent_id}")
|
|
550
|
+
patch_body = PatchedAgentRequest(metadata=metadata_to_update)
|
|
551
|
+
try:
|
|
552
|
+
update_response = agent_partial_update.sync_detailed(
|
|
553
|
+
client=self.client, id=agent_id, body=patch_body
|
|
554
|
+
)
|
|
555
|
+
if update_response.status_code == 200 and update_response.parsed:
|
|
556
|
+
logger.info(
|
|
557
|
+
f"Successfully updated metadata for backend agent ID: {agent_id}."
|
|
558
|
+
)
|
|
559
|
+
return update_response.parsed
|
|
560
|
+
else:
|
|
561
|
+
err_msg = (
|
|
562
|
+
f"Failed to update metadata for backend agent ID: {agent_id}. "
|
|
563
|
+
f"Status: {update_response.status_code}, "
|
|
564
|
+
f"Body: {update_response.content.decode() if update_response.content else 'N/A'}"
|
|
565
|
+
)
|
|
566
|
+
logger.error(err_msg)
|
|
567
|
+
raise RuntimeError(err_msg)
|
|
568
|
+
except Exception as e:
|
|
569
|
+
err_msg_ex = f"Exception during backend agent metadata update for ID: {agent_id}: {e}"
|
|
570
|
+
logger.error(err_msg_ex, exc_info=True)
|
|
571
|
+
raise RuntimeError(err_msg_ex) from e
|
|
572
|
+
|
|
573
|
+
def _create_new_agent(
|
|
574
|
+
self,
|
|
575
|
+
name: str,
|
|
576
|
+
agent_type: AgentTypeEnum,
|
|
577
|
+
endpoint: str,
|
|
578
|
+
metadata: Dict[str, Any],
|
|
579
|
+
description: str,
|
|
580
|
+
) -> BackendAgentModel:
|
|
581
|
+
"""Creates a new agent in the backend."""
|
|
582
|
+
logger.info(
|
|
583
|
+
f"Creating new backend agent: Name='{name}', Type='{agent_type.value}', OrgID='{self.organization_id}' (UUID)"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# IMPORTANT: AgentRequest.organization might expect an int or string representation of UUID.
|
|
587
|
+
# If AgentRequest model expects an int, str(self.organization_id) or another conversion will be needed,
|
|
588
|
+
# or the AgentRequest model itself needs to be updated to accept UUID.
|
|
589
|
+
# For now, passing the UUID directly. This might require AgentRequest model adjustment.
|
|
590
|
+
agent_req_body = AgentRequest(
|
|
591
|
+
name=name,
|
|
592
|
+
endpoint=endpoint,
|
|
593
|
+
agent_type=agent_type,
|
|
594
|
+
metadata=metadata,
|
|
595
|
+
description=description,
|
|
596
|
+
organization=self.organization_id, # Passing UUID here.
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
try:
|
|
600
|
+
create_response = agent_create.sync_detailed(
|
|
601
|
+
client=self.client, body=agent_req_body
|
|
602
|
+
)
|
|
603
|
+
if create_response.status_code == 201 and create_response.parsed:
|
|
604
|
+
logger.info(
|
|
605
|
+
f"Created backend agent '{name}' (Type: {agent_type.value}) "
|
|
606
|
+
f"with ID {create_response.parsed.id}."
|
|
607
|
+
)
|
|
608
|
+
return create_response.parsed
|
|
609
|
+
else:
|
|
610
|
+
body_content = (
|
|
611
|
+
create_response.content.decode()
|
|
612
|
+
if create_response.content
|
|
613
|
+
else "N/A"
|
|
614
|
+
)
|
|
615
|
+
err_msg = (
|
|
616
|
+
f"Failed to create backend agent '{name}'. Status: {create_response.status_code}, "
|
|
617
|
+
f"Body: {body_content}"
|
|
618
|
+
)
|
|
619
|
+
logger.error(err_msg)
|
|
620
|
+
raise RuntimeError(err_msg)
|
|
621
|
+
except Exception as e:
|
|
622
|
+
err_msg_ex = (
|
|
623
|
+
f"Exception during backend agent creation for Name='{name}': {e}"
|
|
624
|
+
)
|
|
625
|
+
logger.error(err_msg_ex, exc_info=True)
|
|
626
|
+
raise RuntimeError(err_msg_ex) from e
|
|
627
|
+
|
|
628
|
+
def ensure_agent_in_backend(
|
|
629
|
+
self,
|
|
630
|
+
name: str,
|
|
631
|
+
agent_type: AgentTypeEnum,
|
|
632
|
+
endpoint_for_backend: str,
|
|
633
|
+
metadata_for_backend: Dict[str, Any],
|
|
634
|
+
description_prefix: str = "Agent managed by router",
|
|
635
|
+
update_metadata_if_exists: bool = True,
|
|
636
|
+
) -> BackendAgentModel:
|
|
637
|
+
"""
|
|
638
|
+
Ensures an agent with the given specifications exists in the backend.
|
|
639
|
+
Uses self.organization_id (UUID) from the router instance.
|
|
640
|
+
"""
|
|
641
|
+
logger.info(
|
|
642
|
+
f"Ensuring backend agent presence: Name='{name}', Type='{agent_type.value}', OrgID='{self.organization_id}' (UUID)"
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
existing_agent = self._find_existing_agent(name=name, agent_type=agent_type)
|
|
646
|
+
|
|
647
|
+
if existing_agent:
|
|
648
|
+
needs_metadata_update = False
|
|
649
|
+
current_metadata = (
|
|
650
|
+
existing_agent.metadata
|
|
651
|
+
if isinstance(existing_agent.metadata, dict)
|
|
652
|
+
else {}
|
|
653
|
+
)
|
|
654
|
+
metadata_to_patch = {}
|
|
655
|
+
|
|
656
|
+
for key, value_for_backend in metadata_for_backend.items():
|
|
657
|
+
if current_metadata.get(key) != value_for_backend:
|
|
658
|
+
metadata_to_patch[key] = value_for_backend
|
|
659
|
+
needs_metadata_update = True
|
|
660
|
+
|
|
661
|
+
if needs_metadata_update and update_metadata_if_exists:
|
|
662
|
+
logger.info(
|
|
663
|
+
f"Backend agent '{name}' exists and metadata needs update. Proceeding with update."
|
|
664
|
+
)
|
|
665
|
+
final_patch_payload = current_metadata.copy()
|
|
666
|
+
final_patch_payload.update(metadata_to_patch)
|
|
667
|
+
|
|
668
|
+
return self._update_agent_metadata(
|
|
669
|
+
agent_id=existing_agent.id, metadata_to_update=final_patch_payload
|
|
670
|
+
)
|
|
671
|
+
else:
|
|
672
|
+
if needs_metadata_update and not update_metadata_if_exists:
|
|
673
|
+
logger.info(
|
|
674
|
+
f"Backend agent '{name}' exists and metadata differs, but update_metadata_if_exists is False. Skipping update."
|
|
675
|
+
)
|
|
676
|
+
else:
|
|
677
|
+
logger.info(
|
|
678
|
+
f"Backend agent '{name}' exists and metadata is current or update is skipped."
|
|
679
|
+
)
|
|
680
|
+
return existing_agent
|
|
681
|
+
|
|
682
|
+
description = f"{description_prefix}: {name}"
|
|
683
|
+
return self._create_new_agent(
|
|
684
|
+
name=name,
|
|
685
|
+
agent_type=agent_type,
|
|
686
|
+
endpoint=endpoint_for_backend,
|
|
687
|
+
metadata=metadata_for_backend,
|
|
688
|
+
description=description,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
def get_agent_instance(self, registration_key: str) -> Agent | None:
|
|
692
|
+
"""
|
|
693
|
+
Retrieves an instantiated agent adapter from the router's registry.
|
|
694
|
+
|
|
695
|
+
Args:
|
|
696
|
+
registration_key: The backend agent's UUID string.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
An instance of the agent adapter, or None if not found.
|
|
700
|
+
"""
|
|
701
|
+
instance = self._agent_registry.get(registration_key)
|
|
702
|
+
if not instance:
|
|
703
|
+
logger.warning(
|
|
704
|
+
f"No agent adapter found in router registry for key: {registration_key}"
|
|
705
|
+
)
|
|
706
|
+
return instance
|
|
707
|
+
|
|
708
|
+
async def route_request(
|
|
709
|
+
self, registration_key: str, request_data: Dict[str, Any]
|
|
710
|
+
) -> Dict[str, Any]:
|
|
711
|
+
"""
|
|
712
|
+
Routes a request to the specified agent and returns its standardized response.
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
registration_key: The backend agent's UUID string.
|
|
716
|
+
request_data: Data for the agent's handle_request method.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
Agent's response or an error dictionary.
|
|
720
|
+
"""
|
|
721
|
+
logger.info(
|
|
722
|
+
f"Routing request for agent with registration key: {registration_key}"
|
|
723
|
+
)
|
|
724
|
+
agent_instance = self.get_agent_instance(registration_key)
|
|
725
|
+
|
|
726
|
+
if not agent_instance:
|
|
727
|
+
logger.error(f"Could not find agent adapter for key: {registration_key}")
|
|
728
|
+
return {
|
|
729
|
+
"error": "AgentNotRegisteredInRouter",
|
|
730
|
+
"message": f"Agent key '{registration_key}' not in router instances.",
|
|
731
|
+
"registration_key": registration_key,
|
|
732
|
+
"status_code": 404,
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
try:
|
|
736
|
+
response = await agent_instance.handle_request(request_data)
|
|
737
|
+
logger.info(
|
|
738
|
+
f"Successfully processed request for agent key '{registration_key}'"
|
|
739
|
+
)
|
|
740
|
+
return response
|
|
741
|
+
except Exception as e:
|
|
742
|
+
logger.error(
|
|
743
|
+
f"Error during request handling by adapter for agent key '{registration_key}': {e}",
|
|
744
|
+
exc_info=True,
|
|
745
|
+
)
|
|
746
|
+
return {
|
|
747
|
+
"error": "RequestHandlingError",
|
|
748
|
+
"message": (
|
|
749
|
+
f"Adapter error for agent key '{registration_key}': {str(e)}"
|
|
750
|
+
),
|
|
751
|
+
"registration_key": registration_key,
|
|
752
|
+
"status_code": 500,
|
|
753
|
+
}
|