quraite 0.1.2__py3-none-any.whl → 0.1.4__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.
- quraite/__init__.py +4 -0
- quraite/adapters/__init__.py +1 -1
- quraite/adapters/agno_adapter.py +50 -92
- quraite/adapters/base.py +26 -76
- quraite/adapters/bedrock_agents_adapter.py +23 -76
- quraite/adapters/flowise_adapter.py +31 -72
- quraite/adapters/google_adk_adapter.py +28 -94
- quraite/adapters/http_adapter.py +28 -44
- quraite/adapters/langchain_adapter.py +51 -118
- quraite/adapters/langchain_server_adapter.py +37 -89
- quraite/adapters/langflow_adapter.py +15 -60
- quraite/adapters/n8n_adapter.py +19 -63
- quraite/adapters/openai_agents_adapter.py +35 -59
- quraite/adapters/pydantic_ai_adapter.py +27 -97
- quraite/adapters/smolagents_adapter.py +21 -82
- quraite/constants/framework.py +14 -0
- quraite/schema/__init__.py +4 -0
- quraite/schema/invoke.py +46 -0
- quraite/schema/message.py +20 -21
- quraite/serve/__init__.py +4 -0
- quraite/serve/cloudflared.py +3 -2
- quraite/serve/server.py +305 -0
- quraite/tracing/__init__.py +8 -5
- quraite/tracing/constants.py +0 -14
- quraite/tracing/setup.py +129 -0
- quraite/tracing/span_exporter.py +6 -6
- quraite/tracing/span_processor.py +6 -7
- quraite/tracing/tool_extractors.py +1 -1
- quraite/tracing/trace.py +36 -24
- quraite/utils/json_utils.py +2 -2
- {quraite-0.1.2.dist-info → quraite-0.1.4.dist-info}/METADATA +54 -62
- quraite-0.1.4.dist-info/RECORD +37 -0
- quraite/schema/response.py +0 -16
- quraite/serve/local_agent.py +0 -360
- quraite/traces/traces_adk_openinference.json +0 -379
- quraite/traces/traces_agno_multi_agent.json +0 -669
- quraite/traces/traces_agno_openinference.json +0 -321
- quraite/traces/traces_crewai_openinference.json +0 -155
- quraite/traces/traces_langgraph_openinference.json +0 -349
- quraite/traces/traces_langgraph_openinference_multi_agent.json +0 -2705
- quraite/traces/traces_langgraph_traceloop.json +0 -510
- quraite/traces/traces_openai_agents_multi_agent_1.json +0 -402
- quraite/traces/traces_openai_agents_openinference.json +0 -341
- quraite/traces/traces_pydantic_openinference.json +0 -286
- quraite/traces/traces_pydantic_openinference_multi_agent_1.json +0 -399
- quraite/traces/traces_pydantic_openinference_multi_agent_2.json +0 -398
- quraite/traces/traces_smol_agents_openinference.json +0 -397
- quraite/traces/traces_smol_agents_tool_calling_openinference.json +0 -704
- quraite-0.1.2.dist-info/RECORD +0 -49
- {quraite-0.1.2.dist-info → quraite-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any
|
|
4
4
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
|
|
7
7
|
from quraite.adapters.base import BaseAdapter
|
|
8
8
|
from quraite.logger import get_logger
|
|
9
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
9
10
|
from quraite.schema.message import (
|
|
10
11
|
AgentMessage,
|
|
11
12
|
AssistantMessage,
|
|
@@ -13,14 +14,13 @@ from quraite.schema.message import (
|
|
|
13
14
|
ToolCall,
|
|
14
15
|
ToolMessage,
|
|
15
16
|
)
|
|
16
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
17
17
|
|
|
18
18
|
logger = get_logger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class FlowiseAdapter(BaseAdapter):
|
|
22
22
|
def __init__(
|
|
23
|
-
self, api_url: str, headers:
|
|
23
|
+
self, api_url: str, headers: dict[str, str] | None = None, timeout: int = 60
|
|
24
24
|
):
|
|
25
25
|
self.api_url = api_url
|
|
26
26
|
self.headers = headers or {}
|
|
@@ -28,8 +28,6 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
28
28
|
|
|
29
29
|
if "Content-Type" not in self.headers:
|
|
30
30
|
self.headers["Content-Type"] = "application/json"
|
|
31
|
-
|
|
32
|
-
self.headers["Content-Type"] = "application/json"
|
|
33
31
|
logger.info(
|
|
34
32
|
"FlowiseAdapter initialized (api_url=%s, timeout=%s)", self.api_url, timeout
|
|
35
33
|
)
|
|
@@ -38,15 +36,15 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
38
36
|
|
|
39
37
|
def _convert_api_output_to_messages(
|
|
40
38
|
self,
|
|
41
|
-
response:
|
|
42
|
-
request_messages:
|
|
43
|
-
) ->
|
|
39
|
+
response: dict[str, Any],
|
|
40
|
+
request_messages: list[AgentMessage] | None = None,
|
|
41
|
+
) -> list[AgentMessage]:
|
|
44
42
|
logger.debug(
|
|
45
43
|
"Converting Flowise response to messages (has_agent_node=%s)",
|
|
46
44
|
bool(response.get("agentFlowExecutedData")),
|
|
47
45
|
)
|
|
48
46
|
|
|
49
|
-
def _append_text(content_obj: Any, bucket:
|
|
47
|
+
def _append_text(content_obj: Any, bucket: list[MessageContentText]) -> None:
|
|
50
48
|
if isinstance(content_obj, str):
|
|
51
49
|
if content_obj:
|
|
52
50
|
bucket.append(MessageContentText(type="text", text=content_obj))
|
|
@@ -60,7 +58,7 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
60
58
|
|
|
61
59
|
agent_node = self._find_agentflow_node(response)
|
|
62
60
|
if agent_node:
|
|
63
|
-
converted:
|
|
61
|
+
converted: list[AgentMessage] = []
|
|
64
62
|
messages_section = agent_node.get("input")
|
|
65
63
|
raw_messages = []
|
|
66
64
|
if isinstance(messages_section, dict):
|
|
@@ -68,7 +66,7 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
68
66
|
if isinstance(potential_messages, list):
|
|
69
67
|
raw_messages = potential_messages
|
|
70
68
|
|
|
71
|
-
latest_turn:
|
|
69
|
+
latest_turn: list[dict[str, Any]] = []
|
|
72
70
|
for raw in reversed(raw_messages):
|
|
73
71
|
if not isinstance(raw, dict):
|
|
74
72
|
continue
|
|
@@ -83,9 +81,9 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
83
81
|
continue
|
|
84
82
|
|
|
85
83
|
if role == "assistant":
|
|
86
|
-
text_content:
|
|
84
|
+
text_content: list[MessageContentText] = []
|
|
87
85
|
_append_text(raw.get("content"), text_content)
|
|
88
|
-
tool_calls_list:
|
|
86
|
+
tool_calls_list: list[ToolCall] = []
|
|
89
87
|
tool_calls = raw.get("tool_calls")
|
|
90
88
|
if isinstance(tool_calls, list):
|
|
91
89
|
for call in tool_calls:
|
|
@@ -128,7 +126,7 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
128
126
|
|
|
129
127
|
output_section = agent_node.get("output")
|
|
130
128
|
if isinstance(output_section, dict):
|
|
131
|
-
final_segments:
|
|
129
|
+
final_segments: list[MessageContentText] = []
|
|
132
130
|
_append_text(output_section.get("content"), final_segments)
|
|
133
131
|
|
|
134
132
|
if final_segments:
|
|
@@ -144,8 +142,8 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
144
142
|
return []
|
|
145
143
|
|
|
146
144
|
def _find_agentflow_node(
|
|
147
|
-
self, response:
|
|
148
|
-
) ->
|
|
145
|
+
self, response: dict[str, Any]
|
|
146
|
+
) -> dict[str, Any] | None:
|
|
149
147
|
executed_data = response.get("agentFlowExecutedData")
|
|
150
148
|
if not isinstance(executed_data, list):
|
|
151
149
|
return None
|
|
@@ -160,39 +158,15 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
160
158
|
|
|
161
159
|
return None
|
|
162
160
|
|
|
163
|
-
def _prepare_input(self, input: List[AgentMessage]) -> str:
|
|
164
|
-
logger.debug("Preparing Flowise input from %d messages", len(input))
|
|
165
|
-
if not input or input[-1].role != "user":
|
|
166
|
-
logger.error("Flowise input missing user message")
|
|
167
|
-
raise ValueError("No user message found in the input")
|
|
168
|
-
|
|
169
|
-
last_user_message = input[-1]
|
|
170
|
-
if not last_user_message.content:
|
|
171
|
-
logger.error("Flowise input user message missing content")
|
|
172
|
-
raise ValueError("User message has no content")
|
|
173
|
-
|
|
174
|
-
text_content = None
|
|
175
|
-
for content_item in last_user_message.content:
|
|
176
|
-
if content_item.type == "text" and content_item.text:
|
|
177
|
-
text_content = content_item.text
|
|
178
|
-
break
|
|
179
|
-
|
|
180
|
-
if not text_content:
|
|
181
|
-
logger.error("Flowise input missing text content")
|
|
182
|
-
raise ValueError("No text content found in user message")
|
|
183
|
-
|
|
184
|
-
logger.debug("Prepared Flowise input (text_length=%d)", len(text_content))
|
|
185
|
-
return text_content
|
|
186
|
-
|
|
187
161
|
async def _aapi_call(
|
|
188
162
|
self,
|
|
189
163
|
question: str,
|
|
190
|
-
history:
|
|
191
|
-
override_config:
|
|
192
|
-
form:
|
|
193
|
-
human_input:
|
|
164
|
+
history: list[dict[str, Any]] | None = None,
|
|
165
|
+
override_config: dict[str, Any] | None = None,
|
|
166
|
+
form: dict[str, Any] | None = None,
|
|
167
|
+
human_input: str | None = None,
|
|
194
168
|
**kwargs,
|
|
195
|
-
) ->
|
|
169
|
+
) -> dict[str, Any]:
|
|
196
170
|
logger.debug(
|
|
197
171
|
"Calling Flowise API (history=%s, form=%s, human_input=%s)",
|
|
198
172
|
bool(history),
|
|
@@ -236,40 +210,25 @@ class FlowiseAdapter(BaseAdapter):
|
|
|
236
210
|
logger.exception("Flowise API response decoding failed")
|
|
237
211
|
raise ValueError(f"Failed to decode JSON response: {e}") from e
|
|
238
212
|
|
|
239
|
-
async def ainvoke(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
session_id: Union[str, None],
|
|
243
|
-
) -> AgentInvocationResponse:
|
|
244
|
-
"""Asynchronous invocation method - invokes the Flowise agent and converts to List[AgentMessage]."""
|
|
245
|
-
logger.info(
|
|
246
|
-
"Flowise ainvoke called (session_id=%s, input_messages=%d)",
|
|
247
|
-
session_id,
|
|
248
|
-
len(input),
|
|
249
|
-
)
|
|
250
|
-
agent_input = self._prepare_input(input)
|
|
213
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
214
|
+
"""Invoke Flowise agent and return response."""
|
|
215
|
+
logger.info("Flowise ainvoke called (session_id=%s)", input.session_id)
|
|
251
216
|
|
|
252
217
|
try:
|
|
253
218
|
agent_output = await self._aapi_call(
|
|
254
|
-
question=
|
|
255
|
-
override_config=
|
|
219
|
+
question=input.user_message_str(),
|
|
220
|
+
override_config=(
|
|
221
|
+
{"sessionId": input.session_id} if input.session_id else None
|
|
222
|
+
),
|
|
256
223
|
)
|
|
257
|
-
|
|
258
|
-
|
|
224
|
+
agent_trajectory = self._convert_api_output_to_messages(
|
|
225
|
+
agent_output, [input.user_message]
|
|
259
226
|
)
|
|
260
|
-
except Exception as e:
|
|
261
|
-
logger.exception("Flowise ainvoke failed during API call")
|
|
262
|
-
raise RuntimeError(f"Error calling Flowise endpoint: {e}") from e
|
|
263
|
-
|
|
264
|
-
try:
|
|
265
|
-
agent_trajectory = self._convert_api_output_to_messages(agent_output, input)
|
|
266
227
|
logger.info(
|
|
267
228
|
"Flowise conversion produced %d trajectory messages",
|
|
268
229
|
len(agent_trajectory),
|
|
269
230
|
)
|
|
270
|
-
return
|
|
271
|
-
agent_trajectory=agent_trajectory,
|
|
272
|
-
)
|
|
231
|
+
return InvokeOutput(agent_trajectory=agent_trajectory)
|
|
273
232
|
except Exception as e:
|
|
274
|
-
logger.exception("
|
|
275
|
-
raise RuntimeError(f"Error
|
|
233
|
+
logger.exception("Error calling Flowise endpoint")
|
|
234
|
+
raise RuntimeError(f"Error calling Flowise endpoint: {e}") from e
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import uuid
|
|
2
|
-
from typing import List, Union
|
|
3
2
|
|
|
4
3
|
from google.adk.agents import Agent
|
|
5
4
|
from google.adk.apps.app import App
|
|
@@ -10,127 +9,62 @@ from google.genai import types
|
|
|
10
9
|
from opentelemetry.trace import TracerProvider
|
|
11
10
|
|
|
12
11
|
from quraite.adapters.base import BaseAdapter
|
|
12
|
+
from quraite.constants.framework import Framework
|
|
13
13
|
from quraite.logger import get_logger
|
|
14
|
-
from quraite.schema.
|
|
15
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
16
|
-
from quraite.tracing.constants import Framework
|
|
14
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
17
15
|
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
18
16
|
|
|
19
17
|
logger = get_logger(__name__)
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
class GoogleADKAdapter(BaseAdapter):
|
|
23
|
-
"""
|
|
24
|
-
Google ADK adapter wrapper that converts any Google ADK agent
|
|
25
|
-
to a standardized callable interface (ainvoke) with tracing support.
|
|
26
|
-
|
|
27
|
-
This class wraps any Google ADK Agent and provides:
|
|
28
|
-
- Asynchronous invocation via ainvoke()
|
|
29
|
-
- OpenTelemetry tracing integration
|
|
30
|
-
- Session management for multi-turn conversations
|
|
31
|
-
"""
|
|
21
|
+
"""Google ADK adapter for Agent (requires tracing)."""
|
|
32
22
|
|
|
33
23
|
def __init__(
|
|
34
24
|
self,
|
|
35
25
|
agent: Agent,
|
|
26
|
+
*,
|
|
27
|
+
tracer_provider: TracerProvider | None = None,
|
|
36
28
|
agent_name: str = "Google ADK Agent",
|
|
37
|
-
tracer_provider: TracerProvider = None,
|
|
38
29
|
app_name: str = "google_adk_agent",
|
|
39
30
|
user_id: str = str(uuid.uuid4()),
|
|
40
31
|
session_service: BaseSessionService = InMemorySessionService(),
|
|
41
32
|
):
|
|
42
33
|
"""
|
|
43
|
-
Initialize
|
|
34
|
+
Initialize Google ADK adapter.
|
|
44
35
|
|
|
45
36
|
Args:
|
|
46
|
-
agent:
|
|
37
|
+
agent: Google ADK Agent instance
|
|
38
|
+
tracer_provider: TracerProvider from setup_tracing() (required)
|
|
39
|
+
agent_name: Agent name for metadata
|
|
47
40
|
app_name: Application name for ADK runner
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
user_id: User ID for session management
|
|
42
|
+
session_service: Session service for multi-turn conversations
|
|
50
43
|
"""
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
self._init_tracing(tracer_provider, required=True)
|
|
44
|
+
if tracer_provider is None:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"Google ADK adapter requires tracing. Use setup_tracing([Framework.GOOGLE_ADK]) first."
|
|
47
|
+
)
|
|
57
48
|
|
|
58
49
|
self.agent: Agent = agent
|
|
59
50
|
self.app_name = app_name
|
|
60
51
|
self.agent_name = agent_name
|
|
61
52
|
self.session_service = session_service
|
|
62
53
|
self.user_id = user_id
|
|
63
|
-
self.app = App(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
self.runner = Runner(
|
|
68
|
-
app=self.app,
|
|
69
|
-
session_service=session_service,
|
|
70
|
-
)
|
|
71
|
-
logger.info("GoogleADKAdapter initialized successfully")
|
|
54
|
+
self.app = App(name=app_name, root_agent=agent)
|
|
55
|
+
self.runner = Runner(app=self.app, session_service=session_service)
|
|
56
|
+
self._init_tracer(tracer_provider)
|
|
57
|
+
logger.info("GoogleADKAdapter initialized")
|
|
72
58
|
|
|
73
|
-
def
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
input: List[Message] containing user_message
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
str: User message text
|
|
82
|
-
"""
|
|
83
|
-
logger.debug("Preparing Google ADK input from %d messages", len(input))
|
|
84
|
-
if not input or input[-1].role != "user":
|
|
85
|
-
logger.error("Google ADK input missing user message")
|
|
86
|
-
raise ValueError("No user message found in the input")
|
|
87
|
-
|
|
88
|
-
last_user_message = input[-1]
|
|
89
|
-
# Check if content list is not empty and has text
|
|
90
|
-
if not last_user_message.content:
|
|
91
|
-
logger.error("Google ADK user message missing content")
|
|
92
|
-
raise ValueError("User message has no content")
|
|
93
|
-
|
|
94
|
-
# Find the first text content item
|
|
95
|
-
text_content = None
|
|
96
|
-
for content_item in last_user_message.content:
|
|
97
|
-
if content_item.type == "text" and content_item.text:
|
|
98
|
-
text_content = content_item.text
|
|
99
|
-
break
|
|
100
|
-
|
|
101
|
-
if not text_content:
|
|
102
|
-
logger.error("Google ADK user message missing text content")
|
|
103
|
-
raise ValueError("No text content found in user message")
|
|
104
|
-
|
|
105
|
-
logger.debug("Prepared Google ADK input (text_length=%d)", len(text_content))
|
|
106
|
-
return text_content
|
|
107
|
-
|
|
108
|
-
async def ainvoke(
|
|
109
|
-
self,
|
|
110
|
-
input: List[AgentMessage],
|
|
111
|
-
session_id: Union[str, None] = None,
|
|
112
|
-
) -> AgentInvocationResponse:
|
|
113
|
-
"""
|
|
114
|
-
Asynchronous invocation method - invokes the Google ADK agent with tracing
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
input: List[AgentMessage] containing user_message
|
|
118
|
-
session_id: Optional conversation ID for maintaining context
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
AgentInvocationResponse - response containing agent trace, trajectory, and final response.
|
|
122
|
-
"""
|
|
123
|
-
logger.info(
|
|
124
|
-
"Google ADK ainvoke called (session_id=%s, input_messages=%d)",
|
|
125
|
-
session_id,
|
|
126
|
-
len(input),
|
|
127
|
-
)
|
|
128
|
-
agent_input = self._prepare_input(input)
|
|
129
|
-
session_id = session_id or str(uuid.uuid4())
|
|
59
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
60
|
+
"""Invoke Google ADK agent and return response with trace."""
|
|
61
|
+
logger.info("Google ADK ainvoke called (session_id=%s)", input.session_id)
|
|
62
|
+
session_id = input.session_id or str(uuid.uuid4())
|
|
130
63
|
|
|
131
64
|
try:
|
|
132
|
-
return await self._ainvoke_with_tracing(
|
|
133
|
-
|
|
65
|
+
return await self._ainvoke_with_tracing(
|
|
66
|
+
input.user_message_str(), session_id
|
|
67
|
+
)
|
|
134
68
|
except Exception as e:
|
|
135
69
|
logger.exception("Error invoking Google ADK agent")
|
|
136
70
|
raise RuntimeError(f"Error invoking Google ADK agent: {e}") from e
|
|
@@ -139,7 +73,7 @@ class GoogleADKAdapter(BaseAdapter):
|
|
|
139
73
|
self,
|
|
140
74
|
agent_input: str,
|
|
141
75
|
session_id: str,
|
|
142
|
-
) ->
|
|
76
|
+
) -> InvokeOutput:
|
|
143
77
|
"""Execute ainvoke with tracing enabled."""
|
|
144
78
|
logger.debug(
|
|
145
79
|
"Starting Google ADK traced invocation (session_id=%s)",
|
|
@@ -203,7 +137,7 @@ class GoogleADKAdapter(BaseAdapter):
|
|
|
203
137
|
else:
|
|
204
138
|
logger.warning("No spans exported for Google ADK trace_id=%s", trace_id)
|
|
205
139
|
|
|
206
|
-
return
|
|
140
|
+
return InvokeOutput(
|
|
207
141
|
agent_trace=agent_trace,
|
|
208
142
|
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
209
143
|
framework=Framework.GOOGLE_ADK
|
quraite/adapters/http_adapter.py
CHANGED
|
@@ -5,14 +5,13 @@ This module provides a BaseAdapter implementation that calls remote agent
|
|
|
5
5
|
servers via HTTP endpoints, enabling distributed agent evaluation.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
|
|
12
12
|
from quraite.adapters.base import BaseAdapter
|
|
13
13
|
from quraite.logger import get_logger
|
|
14
|
-
from quraite.schema.
|
|
15
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
14
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
16
15
|
|
|
17
16
|
logger = get_logger(__name__)
|
|
18
17
|
|
|
@@ -49,7 +48,7 @@ class HttpAdapter(BaseAdapter):
|
|
|
49
48
|
def __init__(
|
|
50
49
|
self,
|
|
51
50
|
url: str,
|
|
52
|
-
headers:
|
|
51
|
+
headers: dict[str, str] | None = None,
|
|
53
52
|
timeout: float = 60.0,
|
|
54
53
|
max_retries: int = 3,
|
|
55
54
|
retry_delay: float = 1.0,
|
|
@@ -79,35 +78,29 @@ class HttpAdapter(BaseAdapter):
|
|
|
79
78
|
|
|
80
79
|
def _serialize_request(
|
|
81
80
|
self,
|
|
82
|
-
input:
|
|
83
|
-
|
|
84
|
-
) -> Dict[str, Any]:
|
|
81
|
+
input: InvokeInput,
|
|
82
|
+
) -> dict[str, Any]:
|
|
85
83
|
"""
|
|
86
84
|
Serialize invocation request to JSON-compatible dict.
|
|
87
85
|
|
|
88
86
|
Args:
|
|
89
|
-
input:
|
|
90
|
-
session_id: Optional conversation ID for maintaining context
|
|
87
|
+
input: InvokeInput containing user_message and session_id
|
|
91
88
|
|
|
92
89
|
Returns:
|
|
93
90
|
Dictionary ready for JSON serialization
|
|
94
91
|
"""
|
|
95
92
|
logger.debug(
|
|
96
|
-
"Serializing HTTP request (
|
|
97
|
-
|
|
98
|
-
session_id,
|
|
93
|
+
"Serializing HTTP request (session_id=%s)",
|
|
94
|
+
input.session_id,
|
|
99
95
|
)
|
|
100
|
-
return
|
|
101
|
-
"input": [msg.model_dump(mode="json") for msg in input],
|
|
102
|
-
"session_id": session_id,
|
|
103
|
-
}
|
|
96
|
+
return input.model_dump(mode="json")
|
|
104
97
|
|
|
105
98
|
async def _make_request_with_retry_async(
|
|
106
99
|
self,
|
|
107
100
|
method: str,
|
|
108
101
|
url: str,
|
|
109
|
-
payload:
|
|
110
|
-
) ->
|
|
102
|
+
payload: dict[str, Any],
|
|
103
|
+
) -> dict[str, Any]:
|
|
111
104
|
"""
|
|
112
105
|
Make HTTP request with retry logic (asynchronous).
|
|
113
106
|
|
|
@@ -151,12 +144,14 @@ class HttpAdapter(BaseAdapter):
|
|
|
151
144
|
# Retry on network errors
|
|
152
145
|
last_exception = e
|
|
153
146
|
if attempt < self.max_retries - 1:
|
|
147
|
+
import asyncio
|
|
148
|
+
|
|
154
149
|
delay = self.retry_delay * (2**attempt)
|
|
155
150
|
logger.warning(
|
|
156
151
|
"HTTP adapter retrying after network/connection error (retry_in=%.2fs)",
|
|
157
152
|
delay,
|
|
158
153
|
)
|
|
159
|
-
await
|
|
154
|
+
await asyncio.sleep(delay)
|
|
160
155
|
else:
|
|
161
156
|
logger.error(
|
|
162
157
|
"Failed while connecting to the url %s after %d attempts. Last error: %s",
|
|
@@ -175,34 +170,24 @@ class HttpAdapter(BaseAdapter):
|
|
|
175
170
|
|
|
176
171
|
raise last_exception
|
|
177
172
|
|
|
178
|
-
@staticmethod
|
|
179
|
-
async def _async_sleep(seconds: float):
|
|
180
|
-
"""Helper for async sleep."""
|
|
181
|
-
import asyncio
|
|
182
|
-
|
|
183
|
-
await asyncio.sleep(seconds)
|
|
184
|
-
|
|
185
173
|
async def ainvoke(
|
|
186
174
|
self,
|
|
187
|
-
input:
|
|
188
|
-
|
|
189
|
-
) -> AgentInvocationResponse:
|
|
175
|
+
input: InvokeInput,
|
|
176
|
+
) -> InvokeOutput:
|
|
190
177
|
"""
|
|
191
178
|
Asynchronously invoke the HTTP agent.
|
|
192
179
|
|
|
193
180
|
Args:
|
|
194
|
-
input:
|
|
195
|
-
session_id: Optional conversation ID for maintaining context
|
|
181
|
+
input: InvokeInput containing user_message and session_id
|
|
196
182
|
|
|
197
183
|
Returns:
|
|
198
|
-
|
|
184
|
+
InvokeOutput: Response containing agent trace and trajectory
|
|
199
185
|
"""
|
|
200
186
|
logger.info(
|
|
201
|
-
"HTTP ainvoke called (session_id=%s
|
|
202
|
-
session_id,
|
|
203
|
-
len(input),
|
|
187
|
+
"HTTP ainvoke called (session_id=%s)",
|
|
188
|
+
input.session_id,
|
|
204
189
|
)
|
|
205
|
-
payload = self._serialize_request(input
|
|
190
|
+
payload = self._serialize_request(input)
|
|
206
191
|
response_data = await self._make_request_with_retry_async(
|
|
207
192
|
method="POST", url=self.url, payload=payload
|
|
208
193
|
)
|
|
@@ -210,9 +195,7 @@ class HttpAdapter(BaseAdapter):
|
|
|
210
195
|
"HTTP adapter received response keys: %s", list(response_data.keys())
|
|
211
196
|
)
|
|
212
197
|
|
|
213
|
-
return
|
|
214
|
-
response_data.get("agent_response", {})
|
|
215
|
-
)
|
|
198
|
+
return InvokeOutput.model_validate(response_data)
|
|
216
199
|
|
|
217
200
|
async def aclose(self):
|
|
218
201
|
"""Close async HTTP client."""
|
|
@@ -232,6 +215,7 @@ if __name__ == "__main__":
|
|
|
232
215
|
import asyncio
|
|
233
216
|
import json
|
|
234
217
|
|
|
218
|
+
from quraite.schema.invoke import InvokeInput
|
|
235
219
|
from quraite.schema.message import MessageContentText, UserMessage
|
|
236
220
|
|
|
237
221
|
async def test_http_adapter():
|
|
@@ -239,12 +223,12 @@ if __name__ == "__main__":
|
|
|
239
223
|
|
|
240
224
|
try:
|
|
241
225
|
response = await adapter.ainvoke(
|
|
242
|
-
input=
|
|
243
|
-
UserMessage(
|
|
226
|
+
input=InvokeInput(
|
|
227
|
+
user_message=UserMessage(
|
|
244
228
|
content=[MessageContentText(text="What is 34354 - 54?")]
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
|
|
229
|
+
),
|
|
230
|
+
session_id="test",
|
|
231
|
+
)
|
|
248
232
|
)
|
|
249
233
|
print(response.agent_trajectory)
|
|
250
234
|
except httpx.HTTPStatusError as e:
|