hackagent 0.3.1__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 +12 -0
- hackagent/agent.py +214 -0
- hackagent/api/__init__.py +1 -0
- hackagent/api/agent/__init__.py +1 -0
- hackagent/api/agent/agent_create.py +347 -0
- hackagent/api/agent/agent_destroy.py +140 -0
- hackagent/api/agent/agent_list.py +242 -0
- hackagent/api/agent/agent_partial_update.py +361 -0
- hackagent/api/agent/agent_retrieve.py +235 -0
- hackagent/api/agent/agent_update.py +361 -0
- hackagent/api/apilogs/__init__.py +1 -0
- hackagent/api/apilogs/apilogs_list.py +170 -0
- hackagent/api/apilogs/apilogs_retrieve.py +162 -0
- hackagent/api/attack/__init__.py +1 -0
- hackagent/api/attack/attack_create.py +275 -0
- hackagent/api/attack/attack_destroy.py +146 -0
- hackagent/api/attack/attack_list.py +254 -0
- hackagent/api/attack/attack_partial_update.py +289 -0
- hackagent/api/attack/attack_retrieve.py +247 -0
- hackagent/api/attack/attack_update.py +289 -0
- hackagent/api/checkout/__init__.py +1 -0
- hackagent/api/checkout/checkout_create.py +225 -0
- hackagent/api/generate/__init__.py +1 -0
- hackagent/api/generate/generate_create.py +253 -0
- hackagent/api/judge/__init__.py +1 -0
- hackagent/api/judge/judge_create.py +253 -0
- hackagent/api/key/__init__.py +1 -0
- hackagent/api/key/key_create.py +179 -0
- hackagent/api/key/key_destroy.py +103 -0
- hackagent/api/key/key_list.py +170 -0
- hackagent/api/key/key_retrieve.py +162 -0
- hackagent/api/organization/__init__.py +1 -0
- hackagent/api/organization/organization_create.py +208 -0
- hackagent/api/organization/organization_destroy.py +104 -0
- hackagent/api/organization/organization_list.py +170 -0
- hackagent/api/organization/organization_me_retrieve.py +126 -0
- hackagent/api/organization/organization_partial_update.py +222 -0
- hackagent/api/organization/organization_retrieve.py +163 -0
- hackagent/api/organization/organization_update.py +222 -0
- hackagent/api/prompt/__init__.py +1 -0
- hackagent/api/prompt/prompt_create.py +171 -0
- hackagent/api/prompt/prompt_destroy.py +104 -0
- hackagent/api/prompt/prompt_list.py +185 -0
- hackagent/api/prompt/prompt_partial_update.py +185 -0
- hackagent/api/prompt/prompt_retrieve.py +163 -0
- hackagent/api/prompt/prompt_update.py +185 -0
- hackagent/api/result/__init__.py +1 -0
- hackagent/api/result/result_create.py +175 -0
- hackagent/api/result/result_destroy.py +106 -0
- hackagent/api/result/result_list.py +249 -0
- hackagent/api/result/result_partial_update.py +193 -0
- hackagent/api/result/result_retrieve.py +167 -0
- hackagent/api/result/result_trace_create.py +177 -0
- hackagent/api/result/result_update.py +189 -0
- hackagent/api/run/__init__.py +1 -0
- hackagent/api/run/run_create.py +187 -0
- hackagent/api/run/run_destroy.py +112 -0
- hackagent/api/run/run_list.py +291 -0
- hackagent/api/run/run_partial_update.py +201 -0
- hackagent/api/run/run_result_create.py +177 -0
- hackagent/api/run/run_retrieve.py +179 -0
- hackagent/api/run/run_run_tests_create.py +187 -0
- hackagent/api/run/run_update.py +201 -0
- hackagent/api/user/__init__.py +1 -0
- hackagent/api/user/user_create.py +212 -0
- hackagent/api/user/user_destroy.py +106 -0
- hackagent/api/user/user_list.py +174 -0
- hackagent/api/user/user_me_retrieve.py +126 -0
- hackagent/api/user/user_me_update.py +196 -0
- hackagent/api/user/user_partial_update.py +226 -0
- hackagent/api/user/user_retrieve.py +167 -0
- hackagent/api/user/user_update.py +226 -0
- hackagent/attacks/AdvPrefix/__init__.py +41 -0
- hackagent/attacks/AdvPrefix/completions.py +416 -0
- hackagent/attacks/AdvPrefix/config.py +259 -0
- hackagent/attacks/AdvPrefix/evaluation.py +745 -0
- hackagent/attacks/AdvPrefix/evaluators.py +564 -0
- hackagent/attacks/AdvPrefix/generate.py +711 -0
- hackagent/attacks/AdvPrefix/utils.py +307 -0
- hackagent/attacks/__init__.py +35 -0
- hackagent/attacks/advprefix.py +507 -0
- hackagent/attacks/base.py +106 -0
- hackagent/attacks/strategies.py +906 -0
- hackagent/cli/__init__.py +19 -0
- hackagent/cli/commands/__init__.py +20 -0
- hackagent/cli/commands/agent.py +100 -0
- hackagent/cli/commands/attack.py +417 -0
- hackagent/cli/commands/config.py +301 -0
- hackagent/cli/commands/results.py +327 -0
- hackagent/cli/config.py +249 -0
- hackagent/cli/main.py +515 -0
- hackagent/cli/tui/__init__.py +31 -0
- hackagent/cli/tui/actions_logger.py +200 -0
- hackagent/cli/tui/app.py +288 -0
- hackagent/cli/tui/base.py +137 -0
- hackagent/cli/tui/logger.py +318 -0
- hackagent/cli/tui/views/__init__.py +33 -0
- hackagent/cli/tui/views/agents.py +488 -0
- hackagent/cli/tui/views/attacks.py +624 -0
- hackagent/cli/tui/views/config.py +244 -0
- hackagent/cli/tui/views/dashboard.py +307 -0
- hackagent/cli/tui/views/results.py +1210 -0
- hackagent/cli/tui/widgets/__init__.py +24 -0
- hackagent/cli/tui/widgets/actions.py +346 -0
- hackagent/cli/tui/widgets/logs.py +435 -0
- hackagent/cli/utils.py +276 -0
- hackagent/client.py +286 -0
- hackagent/errors.py +37 -0
- hackagent/logger.py +83 -0
- hackagent/models/__init__.py +109 -0
- hackagent/models/agent.py +223 -0
- hackagent/models/agent_request.py +129 -0
- hackagent/models/api_token_log.py +184 -0
- hackagent/models/attack.py +154 -0
- hackagent/models/attack_request.py +82 -0
- hackagent/models/checkout_session_request_request.py +76 -0
- hackagent/models/checkout_session_response.py +59 -0
- hackagent/models/choice.py +81 -0
- hackagent/models/choice_message.py +67 -0
- hackagent/models/evaluation_status_enum.py +14 -0
- hackagent/models/generate_error_response.py +59 -0
- hackagent/models/generate_request_request.py +212 -0
- hackagent/models/generate_success_response.py +115 -0
- hackagent/models/generic_error_response.py +70 -0
- hackagent/models/message_request.py +67 -0
- hackagent/models/organization.py +102 -0
- hackagent/models/organization_minimal.py +68 -0
- hackagent/models/organization_request.py +71 -0
- hackagent/models/paginated_agent_list.py +123 -0
- hackagent/models/paginated_api_token_log_list.py +123 -0
- hackagent/models/paginated_attack_list.py +123 -0
- hackagent/models/paginated_organization_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/paginated_user_profile_list.py +123 -0
- hackagent/models/patched_agent_request.py +128 -0
- hackagent/models/patched_attack_request.py +92 -0
- hackagent/models/patched_organization_request.py +71 -0
- hackagent/models/patched_prompt_request.py +125 -0
- hackagent/models/patched_result_request.py +237 -0
- hackagent/models/patched_run_request.py +138 -0
- hackagent/models/patched_user_profile_request.py +99 -0
- hackagent/models/prompt.py +220 -0
- hackagent/models/prompt_request.py +126 -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/usage.py +75 -0
- hackagent/models/user_api_key.py +201 -0
- hackagent/models/user_api_key_request.py +73 -0
- hackagent/models/user_profile.py +135 -0
- hackagent/models/user_profile_minimal.py +76 -0
- hackagent/models/user_profile_request.py +99 -0
- hackagent/router/__init__.py +25 -0
- hackagent/router/adapters/__init__.py +20 -0
- hackagent/router/adapters/base.py +63 -0
- hackagent/router/adapters/google_adk.py +671 -0
- hackagent/router/adapters/litellm_adapter.py +524 -0
- hackagent/router/adapters/openai_adapter.py +426 -0
- hackagent/router/router.py +969 -0
- hackagent/router/types.py +54 -0
- hackagent/tracking/__init__.py +42 -0
- hackagent/tracking/context.py +163 -0
- hackagent/tracking/decorators.py +299 -0
- hackagent/tracking/tracker.py +441 -0
- hackagent/types.py +54 -0
- hackagent/utils.py +194 -0
- hackagent/vulnerabilities/__init__.py +13 -0
- hackagent/vulnerabilities/prompts.py +81 -0
- hackagent-0.3.1.dist-info/METADATA +122 -0
- hackagent-0.3.1.dist-info/RECORD +183 -0
- hackagent-0.3.1.dist-info/WHEEL +4 -0
- hackagent-0.3.1.dist-info/entry_points.txt +2 -0
- hackagent-0.3.1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Copyright 2025 - AI4I. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
# Attempt to import openai, but catch ImportError if not installed.
|
|
21
|
+
try:
|
|
22
|
+
from openai import (
|
|
23
|
+
APIConnectionError,
|
|
24
|
+
APITimeoutError,
|
|
25
|
+
OpenAI,
|
|
26
|
+
OpenAIError,
|
|
27
|
+
RateLimitError,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
OPENAI_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
OpenAI = None # type: ignore
|
|
33
|
+
|
|
34
|
+
# Define dummy exceptions if openai is not available
|
|
35
|
+
class OpenAIError(Exception):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
class APIConnectionError(Exception):
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
class RateLimitError(Exception):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
class APITimeoutError(Exception):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
OPENAI_AVAILABLE = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
from .base import Agent
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# --- Custom Exceptions ---
|
|
54
|
+
class OpenAIConfigurationError(Exception):
|
|
55
|
+
"""Custom exception for OpenAI adapter configuration issues."""
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
logger = logging.getLogger(__name__) # Module-level logger
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class OpenAIAgentAdapter(Agent):
|
|
64
|
+
"""
|
|
65
|
+
Adapter for interacting with AI agents built using the OpenAI SDK.
|
|
66
|
+
|
|
67
|
+
This adapter supports OpenAI's chat completions API, including support for
|
|
68
|
+
function calling and tool use, which are common patterns in agent implementations.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, id: str, config: Dict[str, Any]):
|
|
72
|
+
"""
|
|
73
|
+
Initializes the OpenAIAgentAdapter.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
id: The unique identifier for this OpenAI agent instance.
|
|
77
|
+
config: Configuration dictionary for the OpenAI agent.
|
|
78
|
+
Expected keys:
|
|
79
|
+
- 'name': Model name (e.g., "gpt-4", "gpt-3.5-turbo").
|
|
80
|
+
- 'endpoint' (optional): Base URL for the API (for custom endpoints).
|
|
81
|
+
- 'api_key' (optional): Name of the environment variable holding the API key,
|
|
82
|
+
or the API key itself. Defaults to OPENAI_API_KEY env var.
|
|
83
|
+
- 'max_tokens' (optional): Default max tokens for generation.
|
|
84
|
+
- 'temperature' (optional): Default temperature (defaults to 1.0).
|
|
85
|
+
- 'tools' (optional): List of tool/function definitions for function calling.
|
|
86
|
+
- 'tool_choice' (optional): Controls which tools the model can call.
|
|
87
|
+
"""
|
|
88
|
+
super().__init__(id, config)
|
|
89
|
+
# Use hierarchical logger name for TUI handler inheritance
|
|
90
|
+
self.logger = logging.getLogger(
|
|
91
|
+
f"hackagent.router.adapters.OpenAIAgentAdapter.{self.id}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if not OPENAI_AVAILABLE:
|
|
95
|
+
msg = (
|
|
96
|
+
f"OpenAI SDK is not installed. Please install it with: pip install openai. "
|
|
97
|
+
f"OpenAIAgentAdapter: {self.id}"
|
|
98
|
+
)
|
|
99
|
+
self.logger.error(msg)
|
|
100
|
+
raise OpenAIConfigurationError(msg)
|
|
101
|
+
|
|
102
|
+
if "name" not in self.config:
|
|
103
|
+
msg = f"Missing required configuration key 'name' (for model string) for OpenAIAgentAdapter: {self.id}"
|
|
104
|
+
self.logger.error(msg)
|
|
105
|
+
raise OpenAIConfigurationError(msg)
|
|
106
|
+
|
|
107
|
+
self.model_name: str = self.config["name"]
|
|
108
|
+
self.api_base_url: Optional[str] = self.config.get("endpoint")
|
|
109
|
+
|
|
110
|
+
# Handle API key: can be env var name or the key itself
|
|
111
|
+
api_key_config: Optional[str] = self.config.get("api_key")
|
|
112
|
+
if api_key_config:
|
|
113
|
+
# Try as environment variable first
|
|
114
|
+
self.actual_api_key: Optional[str] = os.environ.get(
|
|
115
|
+
api_key_config, api_key_config
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
# Default to OPENAI_API_KEY environment variable
|
|
119
|
+
self.actual_api_key = os.environ.get("OPENAI_API_KEY")
|
|
120
|
+
|
|
121
|
+
# Initialize OpenAI client
|
|
122
|
+
client_kwargs = {}
|
|
123
|
+
if self.actual_api_key:
|
|
124
|
+
client_kwargs["api_key"] = self.actual_api_key
|
|
125
|
+
if self.api_base_url:
|
|
126
|
+
client_kwargs["base_url"] = self.api_base_url
|
|
127
|
+
|
|
128
|
+
self.client = OpenAI(**client_kwargs)
|
|
129
|
+
|
|
130
|
+
self.logger.info(
|
|
131
|
+
f"OpenAIAgentAdapter '{self.id}' initialized for model: '{self.model_name}'"
|
|
132
|
+
+ (f" API Base: '{self.api_base_url}'" if self.api_base_url else "")
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Store default generation parameters
|
|
136
|
+
self.default_max_tokens = self.config.get("max_tokens")
|
|
137
|
+
self.default_temperature = self.config.get("temperature", 1.0)
|
|
138
|
+
self.default_tools = self.config.get("tools")
|
|
139
|
+
self.default_tool_choice = self.config.get("tool_choice")
|
|
140
|
+
|
|
141
|
+
def _execute_openai_completion(
|
|
142
|
+
self,
|
|
143
|
+
messages: List[Dict[str, str]],
|
|
144
|
+
max_tokens: Optional[int],
|
|
145
|
+
temperature: float,
|
|
146
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
147
|
+
tool_choice: Optional[str] = None,
|
|
148
|
+
**kwargs,
|
|
149
|
+
) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Internal method to generate completions using OpenAI's chat completions API.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
messages: List of message dictionaries with 'role' and 'content'.
|
|
155
|
+
max_tokens: Maximum tokens to generate.
|
|
156
|
+
temperature: Sampling temperature.
|
|
157
|
+
tools: Optional list of tool/function definitions.
|
|
158
|
+
tool_choice: Optional tool choice parameter.
|
|
159
|
+
**kwargs: Additional parameters to pass to the API.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A dictionary containing the response data and metadata.
|
|
163
|
+
"""
|
|
164
|
+
self.logger.info(
|
|
165
|
+
f"Sending request to OpenAI model '{self.model_name}' with {len(messages)} messages..."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
openai_params = {
|
|
170
|
+
"model": self.model_name,
|
|
171
|
+
"messages": messages,
|
|
172
|
+
"temperature": temperature,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if max_tokens is not None:
|
|
176
|
+
openai_params["max_tokens"] = max_tokens
|
|
177
|
+
|
|
178
|
+
if tools:
|
|
179
|
+
openai_params["tools"] = tools
|
|
180
|
+
if tool_choice:
|
|
181
|
+
openai_params["tool_choice"] = tool_choice
|
|
182
|
+
|
|
183
|
+
# Add any additional kwargs
|
|
184
|
+
openai_params.update(kwargs)
|
|
185
|
+
|
|
186
|
+
# Make the API call
|
|
187
|
+
response = self.client.chat.completions.create(**openai_params)
|
|
188
|
+
|
|
189
|
+
# Extract response data
|
|
190
|
+
result = {
|
|
191
|
+
"success": True,
|
|
192
|
+
"message": response.choices[0].message,
|
|
193
|
+
"finish_reason": response.choices[0].finish_reason,
|
|
194
|
+
"usage": response.usage.model_dump() if response.usage else None,
|
|
195
|
+
"model": response.model,
|
|
196
|
+
"raw_response": response,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
self.logger.info(
|
|
200
|
+
f"Successfully received response from OpenAI model '{self.model_name}'. "
|
|
201
|
+
f"Finish reason: {result['finish_reason']}"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
except APITimeoutError as e:
|
|
207
|
+
self.logger.error(
|
|
208
|
+
f"OpenAI API timeout for model '{self.model_name}': {e}", exc_info=True
|
|
209
|
+
)
|
|
210
|
+
return {
|
|
211
|
+
"success": False,
|
|
212
|
+
"error_type": "timeout",
|
|
213
|
+
"error_message": str(e),
|
|
214
|
+
}
|
|
215
|
+
except RateLimitError as e:
|
|
216
|
+
self.logger.error(
|
|
217
|
+
f"OpenAI rate limit exceeded for model '{self.model_name}': {e}",
|
|
218
|
+
exc_info=True,
|
|
219
|
+
)
|
|
220
|
+
return {
|
|
221
|
+
"success": False,
|
|
222
|
+
"error_type": "rate_limit",
|
|
223
|
+
"error_message": str(e),
|
|
224
|
+
}
|
|
225
|
+
except APIConnectionError as e:
|
|
226
|
+
self.logger.error(
|
|
227
|
+
f"OpenAI API connection error for model '{self.model_name}': {e}",
|
|
228
|
+
exc_info=True,
|
|
229
|
+
)
|
|
230
|
+
return {
|
|
231
|
+
"success": False,
|
|
232
|
+
"error_type": "connection",
|
|
233
|
+
"error_message": str(e),
|
|
234
|
+
}
|
|
235
|
+
except OpenAIError as e:
|
|
236
|
+
self.logger.error(
|
|
237
|
+
f"OpenAI API error for model '{self.model_name}': {e}", exc_info=True
|
|
238
|
+
)
|
|
239
|
+
return {
|
|
240
|
+
"success": False,
|
|
241
|
+
"error_type": "api_error",
|
|
242
|
+
"error_message": str(e),
|
|
243
|
+
}
|
|
244
|
+
except Exception as e:
|
|
245
|
+
self.logger.exception(
|
|
246
|
+
f"Unexpected error during OpenAI completion for model '{self.model_name}': {e}"
|
|
247
|
+
)
|
|
248
|
+
return {
|
|
249
|
+
"success": False,
|
|
250
|
+
"error_type": "unexpected",
|
|
251
|
+
"error_message": f"{type(e).__name__}: {str(e)}",
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
def handle_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Handles an incoming request by processing it through the OpenAI API.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
request_data: A dictionary containing the request data.
|
|
260
|
+
Expected keys:
|
|
261
|
+
- 'prompt': The text prompt to send to the model (will be converted to messages).
|
|
262
|
+
- 'messages' (optional): Pre-formatted messages list (takes precedence over prompt).
|
|
263
|
+
- 'max_tokens' (optional): Override default max tokens.
|
|
264
|
+
- 'temperature' (optional): Override default temperature.
|
|
265
|
+
- 'tools' (optional): Override default tools.
|
|
266
|
+
- 'tool_choice' (optional): Override default tool choice.
|
|
267
|
+
- Any other kwargs to pass to the OpenAI API.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
A dictionary representing the agent's response or an error.
|
|
271
|
+
"""
|
|
272
|
+
# Get messages or convert prompt to messages
|
|
273
|
+
messages = request_data.get("messages")
|
|
274
|
+
prompt_text = request_data.get("prompt")
|
|
275
|
+
|
|
276
|
+
if not messages and not prompt_text:
|
|
277
|
+
self.logger.warning("No 'messages' or 'prompt' found in request_data.")
|
|
278
|
+
return self._build_error_response(
|
|
279
|
+
error_message="Request data must include either 'messages' or 'prompt' field.",
|
|
280
|
+
status_code=400,
|
|
281
|
+
raw_request=request_data,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Convert prompt to messages if messages not provided
|
|
285
|
+
if not messages:
|
|
286
|
+
messages = [{"role": "user", "content": prompt_text}]
|
|
287
|
+
|
|
288
|
+
self.logger.info(
|
|
289
|
+
f"Handling request for OpenAI adapter {self.id} with {len(messages)} messages"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Get parameters with defaults
|
|
293
|
+
max_tokens = request_data.get("max_tokens", self.default_max_tokens)
|
|
294
|
+
temperature = request_data.get("temperature", self.default_temperature)
|
|
295
|
+
tools = request_data.get("tools", self.default_tools)
|
|
296
|
+
tool_choice = request_data.get("tool_choice", self.default_tool_choice)
|
|
297
|
+
|
|
298
|
+
# Get additional kwargs (exclude known parameters)
|
|
299
|
+
excluded_keys = {
|
|
300
|
+
"prompt",
|
|
301
|
+
"messages",
|
|
302
|
+
"max_tokens",
|
|
303
|
+
"temperature",
|
|
304
|
+
"tools",
|
|
305
|
+
"tool_choice",
|
|
306
|
+
}
|
|
307
|
+
additional_kwargs = {
|
|
308
|
+
k: v for k, v in request_data.items() if k not in excluded_keys
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
# Execute the completion
|
|
313
|
+
result = self._execute_openai_completion(
|
|
314
|
+
messages=messages,
|
|
315
|
+
max_tokens=max_tokens,
|
|
316
|
+
temperature=temperature,
|
|
317
|
+
tools=tools,
|
|
318
|
+
tool_choice=tool_choice,
|
|
319
|
+
**additional_kwargs,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if not result.get("success"):
|
|
323
|
+
# Handle API errors
|
|
324
|
+
return self._build_error_response(
|
|
325
|
+
error_message=f"OpenAI API error ({result.get('error_type')}): {result.get('error_message')}",
|
|
326
|
+
status_code=500,
|
|
327
|
+
raw_request=request_data,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Extract the generated text
|
|
331
|
+
message = result["message"]
|
|
332
|
+
content = message.content if message.content else ""
|
|
333
|
+
|
|
334
|
+
# For reasoning models (e.g., o1-preview, o1-mini), check reasoning field
|
|
335
|
+
if not content and hasattr(message, "reasoning") and message.reasoning:
|
|
336
|
+
generated_text = message.reasoning
|
|
337
|
+
self.logger.info(
|
|
338
|
+
f"OpenAI extracted text from 'reasoning' field (reasoning model) for '{self.model_name}'"
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
generated_text = content
|
|
342
|
+
|
|
343
|
+
# Check if there are tool calls
|
|
344
|
+
tool_calls = None
|
|
345
|
+
if hasattr(message, "tool_calls") and message.tool_calls:
|
|
346
|
+
tool_calls = [
|
|
347
|
+
{
|
|
348
|
+
"id": tc.id,
|
|
349
|
+
"type": tc.type,
|
|
350
|
+
"function": {
|
|
351
|
+
"name": tc.function.name,
|
|
352
|
+
"arguments": tc.function.arguments,
|
|
353
|
+
},
|
|
354
|
+
}
|
|
355
|
+
for tc in message.tool_calls
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
self.logger.info(
|
|
359
|
+
f"Successfully processed request for OpenAI adapter {self.id}."
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
"raw_request": request_data,
|
|
364
|
+
"generated_text": generated_text,
|
|
365
|
+
"processed_response": generated_text,
|
|
366
|
+
"status_code": 200,
|
|
367
|
+
"raw_response_headers": None,
|
|
368
|
+
"raw_response_body": None,
|
|
369
|
+
"agent_specific_data": {
|
|
370
|
+
"model_name": self.model_name,
|
|
371
|
+
"finish_reason": result["finish_reason"],
|
|
372
|
+
"usage": result.get("usage"),
|
|
373
|
+
"tool_calls": tool_calls,
|
|
374
|
+
"invoked_parameters": {
|
|
375
|
+
"max_tokens": max_tokens,
|
|
376
|
+
"temperature": temperature,
|
|
377
|
+
"tools_provided": tools is not None,
|
|
378
|
+
**additional_kwargs,
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
"error_message": None,
|
|
382
|
+
"agent_id": self.id,
|
|
383
|
+
"adapter_type": "OpenAIAgentAdapter",
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
except Exception as e:
|
|
387
|
+
self.logger.exception(
|
|
388
|
+
f"Unexpected error in OpenAIAgentAdapter handle_request for agent {self.id}: {e}"
|
|
389
|
+
)
|
|
390
|
+
return self._build_error_response(
|
|
391
|
+
error_message=f"Unexpected adapter error: {type(e).__name__} - {str(e)}",
|
|
392
|
+
status_code=500,
|
|
393
|
+
raw_request=request_data,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
def _build_error_response(
|
|
397
|
+
self,
|
|
398
|
+
error_message: str,
|
|
399
|
+
status_code: Optional[int],
|
|
400
|
+
raw_request: Optional[Dict[str, Any]] = None,
|
|
401
|
+
) -> Dict[str, Any]:
|
|
402
|
+
"""
|
|
403
|
+
Constructs a standardized error response dictionary for the adapter.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
error_message: The primary error message string.
|
|
407
|
+
status_code: The HTTP status code associated with the error.
|
|
408
|
+
raw_request: The original request data that led to the error.
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
A dictionary representing a standardized error response.
|
|
412
|
+
"""
|
|
413
|
+
return {
|
|
414
|
+
"raw_request": raw_request,
|
|
415
|
+
"processed_response": None,
|
|
416
|
+
"generated_text": None,
|
|
417
|
+
"status_code": status_code if status_code is not None else 500,
|
|
418
|
+
"raw_response_headers": None,
|
|
419
|
+
"raw_response_body": None,
|
|
420
|
+
"agent_specific_data": {
|
|
421
|
+
"model_name": self.model_name if hasattr(self, "model_name") else "N/A"
|
|
422
|
+
},
|
|
423
|
+
"error_message": error_message,
|
|
424
|
+
"agent_id": self.id,
|
|
425
|
+
"adapter_type": "OpenAIAgentAdapter",
|
|
426
|
+
}
|