quraite 0.1.3__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/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.3.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 -361
- 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.3.dist-info/RECORD +0 -49
- {quraite-0.1.3.dist-info → quraite-0.1.4.dist-info}/WHEEL +0 -0
quraite/adapters/n8n_adapter.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import uuid
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any
|
|
5
5
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
|
|
8
8
|
from quraite.adapters.base import BaseAdapter
|
|
9
9
|
from quraite.logger import get_logger
|
|
10
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
10
11
|
from quraite.schema.message import (
|
|
11
12
|
AgentMessage,
|
|
12
13
|
AssistantMessage,
|
|
@@ -14,14 +15,13 @@ from quraite.schema.message import (
|
|
|
14
15
|
ToolCall,
|
|
15
16
|
ToolMessage,
|
|
16
17
|
)
|
|
17
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
18
18
|
|
|
19
19
|
logger = get_logger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class N8nAdapter(BaseAdapter):
|
|
23
23
|
def __init__(
|
|
24
|
-
self, api_url: str, headers:
|
|
24
|
+
self, api_url: str, headers: dict[str, str] | None = None, timeout: int = 60
|
|
25
25
|
):
|
|
26
26
|
self.api_url = api_url
|
|
27
27
|
self.headers = headers or {}
|
|
@@ -35,13 +35,13 @@ class N8nAdapter(BaseAdapter):
|
|
|
35
35
|
|
|
36
36
|
def _convert_api_output_to_messages(
|
|
37
37
|
self,
|
|
38
|
-
response:
|
|
39
|
-
) ->
|
|
38
|
+
response: dict[str, Any],
|
|
39
|
+
) -> list[AgentMessage]:
|
|
40
40
|
logger.debug(
|
|
41
41
|
"Converting n8n response (steps=%d)",
|
|
42
42
|
len(response[0].get("intermediateSteps", [])),
|
|
43
43
|
)
|
|
44
|
-
messages:
|
|
44
|
+
messages: list[AgentMessage] = []
|
|
45
45
|
output = response[0]["output"]
|
|
46
46
|
intermediateSteps = response[0]["intermediateSteps"]
|
|
47
47
|
|
|
@@ -52,10 +52,10 @@ class N8nAdapter(BaseAdapter):
|
|
|
52
52
|
)
|
|
53
53
|
]
|
|
54
54
|
|
|
55
|
-
def flush_messages(tool_calls_dict:
|
|
55
|
+
def flush_messages(tool_calls_dict: dict[str, Any]):
|
|
56
56
|
nonlocal messages
|
|
57
|
-
tool_calls_list:
|
|
58
|
-
tool_results:
|
|
57
|
+
tool_calls_list: list[ToolCall] = []
|
|
58
|
+
tool_results: list[ToolMessage] = []
|
|
59
59
|
|
|
60
60
|
for tool_call_id, tool_call_dict in tool_calls_dict.items():
|
|
61
61
|
tool_name = tool_call_dict.get("name", "")
|
|
@@ -87,7 +87,7 @@ class N8nAdapter(BaseAdapter):
|
|
|
87
87
|
|
|
88
88
|
messages.extend(tool_results)
|
|
89
89
|
|
|
90
|
-
current_step_tool_calls_dict:
|
|
90
|
+
current_step_tool_calls_dict: dict[str, Any] = {}
|
|
91
91
|
for step in intermediateSteps:
|
|
92
92
|
message_log = step.get("action", {}).get("messageLog", {})
|
|
93
93
|
if message_log:
|
|
@@ -127,35 +127,11 @@ class N8nAdapter(BaseAdapter):
|
|
|
127
127
|
)
|
|
128
128
|
return messages
|
|
129
129
|
|
|
130
|
-
def _prepare_input(self, input: List[AgentMessage]) -> str:
|
|
131
|
-
logger.debug("Preparing n8n input from %d messages", len(input))
|
|
132
|
-
if not input or input[-1].role != "user":
|
|
133
|
-
logger.error("n8n input missing user message")
|
|
134
|
-
raise ValueError("No user message found in the input")
|
|
135
|
-
|
|
136
|
-
last_user_message = input[-1]
|
|
137
|
-
if not last_user_message.content:
|
|
138
|
-
logger.error("n8n user message missing content")
|
|
139
|
-
raise ValueError("User message has no content")
|
|
140
|
-
|
|
141
|
-
text_content = None
|
|
142
|
-
for content_item in last_user_message.content:
|
|
143
|
-
if content_item.type == "text" and content_item.text:
|
|
144
|
-
text_content = content_item.text
|
|
145
|
-
break
|
|
146
|
-
|
|
147
|
-
if not text_content:
|
|
148
|
-
logger.error("n8n user message missing text content")
|
|
149
|
-
raise ValueError("No text content found in user message")
|
|
150
|
-
|
|
151
|
-
logger.debug("Prepared n8n input (text_length=%d)", len(text_content))
|
|
152
|
-
return text_content
|
|
153
|
-
|
|
154
130
|
async def _aapi_call(
|
|
155
131
|
self,
|
|
156
132
|
query: str,
|
|
157
133
|
sessionId: str,
|
|
158
|
-
) ->
|
|
134
|
+
) -> dict[str, Any]:
|
|
159
135
|
payload = {
|
|
160
136
|
"query": query,
|
|
161
137
|
"sessionId": sessionId,
|
|
@@ -183,38 +159,18 @@ class N8nAdapter(BaseAdapter):
|
|
|
183
159
|
logger.exception("n8n API response decoding failed")
|
|
184
160
|
raise ValueError(f"Failed to decode JSON response: {e}") from e
|
|
185
161
|
|
|
186
|
-
async def ainvoke(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
session_id: Union[str, None],
|
|
190
|
-
) -> AgentInvocationResponse:
|
|
191
|
-
logger.info(
|
|
192
|
-
"n8n ainvoke called (session_id=%s, input_messages=%d)",
|
|
193
|
-
session_id,
|
|
194
|
-
len(input),
|
|
195
|
-
)
|
|
196
|
-
agent_input = self._prepare_input(input)
|
|
162
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
163
|
+
"""Invoke n8n agent and return response."""
|
|
164
|
+
logger.info("n8n ainvoke called (session_id=%s)", input.session_id)
|
|
197
165
|
|
|
198
166
|
try:
|
|
199
167
|
agent_output = await self._aapi_call(
|
|
200
|
-
query=
|
|
201
|
-
sessionId=session_id if session_id else uuid.uuid4(),
|
|
202
|
-
)
|
|
203
|
-
logger.debug(
|
|
204
|
-
"n8n API returned payload keys: %s", list(agent_output[0].keys())
|
|
168
|
+
query=input.user_message_str(),
|
|
169
|
+
sessionId=input.session_id if input.session_id else str(uuid.uuid4()),
|
|
205
170
|
)
|
|
171
|
+
agent_trajectory = self._convert_api_output_to_messages(agent_output)
|
|
172
|
+
logger.info("n8n produced %d trajectory messages", len(agent_trajectory))
|
|
173
|
+
return InvokeOutput(agent_trajectory=agent_trajectory)
|
|
206
174
|
except Exception as e:
|
|
207
175
|
logger.exception("Error calling n8n endpoint")
|
|
208
176
|
raise RuntimeError(f"Error calling n8n endpoint: {e}") from e
|
|
209
|
-
|
|
210
|
-
try:
|
|
211
|
-
agent_trajectory = self._convert_api_output_to_messages(agent_output)
|
|
212
|
-
logger.info(
|
|
213
|
-
"n8n conversion produced %d trajectory messages", len(agent_trajectory)
|
|
214
|
-
)
|
|
215
|
-
return AgentInvocationResponse(
|
|
216
|
-
agent_trajectory=agent_trajectory,
|
|
217
|
-
)
|
|
218
|
-
except Exception as e:
|
|
219
|
-
logger.exception("Error processing n8n response")
|
|
220
|
-
raise RuntimeError(f"Error processing n8n response: {e}") from e
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Dict, List, Optional, Union
|
|
3
2
|
|
|
4
3
|
from agents import (
|
|
5
4
|
Agent,
|
|
@@ -16,7 +15,9 @@ from agents.memory import Session
|
|
|
16
15
|
from opentelemetry.trace import TracerProvider
|
|
17
16
|
|
|
18
17
|
from quraite.adapters.base import BaseAdapter
|
|
18
|
+
from quraite.constants.framework import Framework
|
|
19
19
|
from quraite.logger import get_logger
|
|
20
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
20
21
|
from quraite.schema.message import (
|
|
21
22
|
AgentMessage,
|
|
22
23
|
AssistantMessage,
|
|
@@ -25,38 +26,46 @@ from quraite.schema.message import (
|
|
|
25
26
|
ToolCall,
|
|
26
27
|
ToolMessage,
|
|
27
28
|
)
|
|
28
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
29
|
-
from quraite.tracing.constants import Framework
|
|
30
29
|
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
31
30
|
|
|
32
31
|
logger = get_logger(__name__)
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
class OpenaiAgentsAdapter(BaseAdapter):
|
|
35
|
+
"""OpenAI Agents adapter for Agent."""
|
|
36
|
+
|
|
36
37
|
def __init__(
|
|
37
38
|
self,
|
|
38
39
|
agent: Agent,
|
|
40
|
+
*,
|
|
41
|
+
tracer_provider: TracerProvider | None = None,
|
|
39
42
|
agent_name: str = "OpenAI Agents",
|
|
40
|
-
tracer_provider: Optional[TracerProvider] = None,
|
|
41
43
|
):
|
|
44
|
+
"""
|
|
45
|
+
Initialize OpenAI Agents adapter.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
agent: OpenAI Agent instance
|
|
49
|
+
tracer_provider: Optional TracerProvider from setup_tracing()
|
|
50
|
+
agent_name: Agent name for metadata
|
|
51
|
+
"""
|
|
42
52
|
self.agent = agent
|
|
43
|
-
self.sessions:
|
|
44
|
-
self._init_tracing(tracer_provider, required=False)
|
|
53
|
+
self.sessions: dict[str, Session] = {}
|
|
45
54
|
self.agent_name = agent_name
|
|
55
|
+
self._init_tracer(tracer_provider)
|
|
46
56
|
logger.info(
|
|
47
|
-
"OpenaiAgentsAdapter initialized (
|
|
48
|
-
agent_name,
|
|
49
|
-
bool(tracer_provider),
|
|
57
|
+
"OpenaiAgentsAdapter initialized (tracing=%s)", bool(tracer_provider)
|
|
50
58
|
)
|
|
51
59
|
|
|
52
60
|
def _convert_run_items_to_messages(
|
|
53
|
-
self, run_items:
|
|
54
|
-
) ->
|
|
61
|
+
self, run_items: list[RunItem]
|
|
62
|
+
) -> list[AgentMessage]:
|
|
63
|
+
"""Convert OpenAI run items to Quraite messages."""
|
|
55
64
|
logger.debug("Converting %d OpenAI run items to messages", len(run_items))
|
|
56
|
-
messages:
|
|
57
|
-
text_content:
|
|
58
|
-
reasoning_content:
|
|
59
|
-
tool_calls:
|
|
65
|
+
messages: list[AgentMessage] = []
|
|
66
|
+
text_content: list[MessageContentText] = []
|
|
67
|
+
reasoning_content: list[MessageContentReasoning] = []
|
|
68
|
+
tool_calls: list[ToolCall] = []
|
|
60
69
|
|
|
61
70
|
def flush_assistant_message():
|
|
62
71
|
nonlocal text_content, reasoning_content, tool_calls
|
|
@@ -141,46 +150,13 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
141
150
|
logger.info("Converted OpenAI agent run into %d messages", len(messages))
|
|
142
151
|
return messages
|
|
143
152
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
if not input or input[-1].role != "user":
|
|
147
|
-
logger.error("OpenAI input missing user message")
|
|
148
|
-
raise ValueError("No user message found in the input")
|
|
149
|
-
|
|
150
|
-
last_user_message = input[-1]
|
|
151
|
-
if not last_user_message.content:
|
|
152
|
-
logger.error("OpenAI user message missing content")
|
|
153
|
-
raise ValueError("User message has no content")
|
|
154
|
-
|
|
155
|
-
text_content = None
|
|
156
|
-
for content_item in last_user_message.content:
|
|
157
|
-
if content_item.type == "text" and content_item.text:
|
|
158
|
-
text_content = content_item.text
|
|
159
|
-
break
|
|
160
|
-
|
|
161
|
-
if not text_content:
|
|
162
|
-
logger.error("OpenAI user message missing text content")
|
|
163
|
-
raise ValueError("No text content found in user message")
|
|
164
|
-
|
|
165
|
-
logger.debug("Prepared OpenAI input (text_length=%d)", len(text_content))
|
|
166
|
-
return text_content
|
|
167
|
-
|
|
168
|
-
async def ainvoke(
|
|
169
|
-
self,
|
|
170
|
-
input: List[AgentMessage],
|
|
171
|
-
session_id: Union[str, None] = None,
|
|
172
|
-
) -> AgentInvocationResponse:
|
|
173
|
-
"""Asynchronous invocation method - invokes the OpenAI Agents agent and converts to List[AgentMessage]."""
|
|
153
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
154
|
+
"""Invoke OpenAI Agents agent and return response."""
|
|
174
155
|
try:
|
|
175
|
-
logger.info(
|
|
176
|
-
|
|
177
|
-
session_id,
|
|
178
|
-
len(input),
|
|
179
|
-
)
|
|
180
|
-
agent_input: Union[str, List[TResponseInputItem]] = self._prepare_input(
|
|
181
|
-
input
|
|
182
|
-
)
|
|
156
|
+
logger.info("OpenAI ainvoke called (session_id=%s)", input.session_id)
|
|
157
|
+
agent_input: str | list[TResponseInputItem] = input.user_message_str()
|
|
183
158
|
|
|
159
|
+
session_id = input.session_id or "default"
|
|
184
160
|
if session_id not in self.sessions:
|
|
185
161
|
self.sessions[session_id] = SQLiteSession(session_id=session_id)
|
|
186
162
|
session = self.sessions[session_id]
|
|
@@ -195,9 +171,9 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
195
171
|
|
|
196
172
|
async def _ainvoke_with_tracing(
|
|
197
173
|
self,
|
|
198
|
-
agent_input:
|
|
174
|
+
agent_input: str | list[TResponseInputItem],
|
|
199
175
|
session: Session,
|
|
200
|
-
) ->
|
|
176
|
+
) -> InvokeOutput:
|
|
201
177
|
"""Execute ainvoke with tracing enabled."""
|
|
202
178
|
with self.tracer.start_as_current_span("openai_invocation") as span:
|
|
203
179
|
trace_id = span.get_span_context().trace_id
|
|
@@ -234,7 +210,7 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
234
210
|
trace_id,
|
|
235
211
|
)
|
|
236
212
|
|
|
237
|
-
return
|
|
213
|
+
return InvokeOutput(
|
|
238
214
|
agent_trace=agent_trace,
|
|
239
215
|
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
240
216
|
framework=Framework.OPENAI_AGENTS
|
|
@@ -243,9 +219,9 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
243
219
|
|
|
244
220
|
async def _ainvoke_without_tracing(
|
|
245
221
|
self,
|
|
246
|
-
agent_input:
|
|
222
|
+
agent_input: str | list[TResponseInputItem],
|
|
247
223
|
session: Session,
|
|
248
|
-
) ->
|
|
224
|
+
) -> InvokeOutput:
|
|
249
225
|
"""Execute ainvoke without tracing."""
|
|
250
226
|
result = await Runner.run(
|
|
251
227
|
self.agent,
|
|
@@ -259,7 +235,7 @@ class OpenaiAgentsAdapter(BaseAdapter):
|
|
|
259
235
|
"OpenAI agent produced %d trajectory messages (no tracing)",
|
|
260
236
|
len(agent_trajectory),
|
|
261
237
|
)
|
|
262
|
-
return
|
|
238
|
+
return InvokeOutput(
|
|
263
239
|
agent_trajectory=agent_trajectory,
|
|
264
240
|
)
|
|
265
241
|
except Exception as exc:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from opentelemetry.trace import TracerProvider
|
|
4
4
|
from pydantic_ai import Agent
|
|
@@ -12,7 +12,9 @@ from pydantic_ai.messages import (
|
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
from quraite.adapters.base import BaseAdapter
|
|
15
|
+
from quraite.constants.framework import Framework
|
|
15
16
|
from quraite.logger import get_logger
|
|
17
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
16
18
|
from quraite.schema.message import (
|
|
17
19
|
AgentMessage,
|
|
18
20
|
AssistantMessage,
|
|
@@ -20,69 +22,47 @@ from quraite.schema.message import (
|
|
|
20
22
|
ToolCall,
|
|
21
23
|
ToolMessage,
|
|
22
24
|
)
|
|
23
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
24
|
-
from quraite.tracing.constants import Framework
|
|
25
25
|
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
26
26
|
|
|
27
27
|
logger = get_logger(__name__)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class PydanticAIAdapter(BaseAdapter):
|
|
31
|
-
"""
|
|
32
|
-
Pydantic AI adapter wrapper that converts any Pydantic AI agent
|
|
33
|
-
to a standardized callable interface (invoke) and converts the output to List[AgentMessage].
|
|
34
|
-
|
|
35
|
-
This class wraps any Pydantic AI Agent and provides:
|
|
36
|
-
- Synchronous invocation via invoke()
|
|
37
|
-
- Asynchronous invocation via ainvoke()
|
|
38
|
-
- Automatic conversion to List[AgentMessage] format
|
|
39
|
-
- Access to message history and traces
|
|
40
|
-
"""
|
|
31
|
+
"""Pydantic AI adapter for Agent."""
|
|
41
32
|
|
|
42
33
|
def __init__(
|
|
43
34
|
self,
|
|
44
35
|
agent: Agent,
|
|
45
|
-
|
|
36
|
+
*,
|
|
46
37
|
tracer_provider: TracerProvider | None = None,
|
|
38
|
+
agent_name: str = "Pydantic AI Agent",
|
|
47
39
|
):
|
|
48
40
|
"""
|
|
49
|
-
Initialize
|
|
41
|
+
Initialize Pydantic AI adapter.
|
|
50
42
|
|
|
51
43
|
Args:
|
|
52
|
-
agent:
|
|
53
|
-
|
|
44
|
+
agent: Pydantic AI Agent instance
|
|
45
|
+
tracer_provider: Optional TracerProvider from setup_tracing()
|
|
46
|
+
agent_name: Agent name for metadata
|
|
54
47
|
"""
|
|
55
48
|
self.agent = agent
|
|
56
49
|
self.agent_name = agent_name
|
|
57
|
-
# Store session state for conversation context
|
|
58
50
|
self._sessions: dict[str, Any] = {}
|
|
59
|
-
self.
|
|
60
|
-
logger.info(
|
|
61
|
-
"PydanticAIAdapter initialized (agent_name=%s, tracing_enabled=%s)",
|
|
62
|
-
agent_name,
|
|
63
|
-
bool(tracer_provider),
|
|
64
|
-
)
|
|
51
|
+
self._init_tracer(tracer_provider)
|
|
52
|
+
logger.info("PydanticAIAdapter initialized (tracing=%s)", bool(tracer_provider))
|
|
65
53
|
|
|
66
54
|
def _convert_pydantic_ai_messages_to_messages(
|
|
67
|
-
self, messages:
|
|
68
|
-
) ->
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
messages: List of Pydantic AI ModelMessage objects
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
List[AgentMessage]: Converted messages in SDK format
|
|
77
|
-
"""
|
|
78
|
-
converted_messages: List[AgentMessage] = []
|
|
55
|
+
self, messages: list[Any]
|
|
56
|
+
) -> list[AgentMessage]:
|
|
57
|
+
"""Convert Pydantic AI messages to Quraite format."""
|
|
58
|
+
converted_messages: list[AgentMessage] = []
|
|
79
59
|
|
|
80
60
|
for msg in messages:
|
|
81
61
|
if not hasattr(msg, "parts"):
|
|
82
62
|
continue
|
|
83
63
|
|
|
84
|
-
content:
|
|
85
|
-
tool_calls_list:
|
|
64
|
+
content: list[MessageContentText] = []
|
|
65
|
+
tool_calls_list: list[ToolCall] = []
|
|
86
66
|
|
|
87
67
|
for part in msg.parts:
|
|
88
68
|
if isinstance(part, SystemPromptPart) or isinstance(
|
|
@@ -147,61 +127,11 @@ class PydanticAIAdapter(BaseAdapter):
|
|
|
147
127
|
)
|
|
148
128
|
return converted_messages
|
|
149
129
|
|
|
150
|
-
def
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
input: List[AgentMessage] containing user_message
|
|
156
|
-
|
|
157
|
-
Returns:
|
|
158
|
-
str: User message text
|
|
159
|
-
"""
|
|
160
|
-
logger.debug("Preparing Pydantic AI input from %d messages", len(input))
|
|
161
|
-
if not input or input[-1].role != "user":
|
|
162
|
-
logger.error("Pydantic AI input missing user message")
|
|
163
|
-
raise ValueError("No user message found in the input")
|
|
164
|
-
|
|
165
|
-
last_user_message = input[-1]
|
|
166
|
-
if not last_user_message.content:
|
|
167
|
-
logger.error("Pydantic AI user message missing content")
|
|
168
|
-
raise ValueError("User message has no content")
|
|
169
|
-
|
|
170
|
-
text_content = None
|
|
171
|
-
for content_item in last_user_message.content:
|
|
172
|
-
if content_item.type == "text" and content_item.text:
|
|
173
|
-
text_content = content_item.text
|
|
174
|
-
break
|
|
175
|
-
|
|
176
|
-
if not text_content:
|
|
177
|
-
logger.error("Pydantic AI user message missing text content")
|
|
178
|
-
raise ValueError("No text content found in user message")
|
|
179
|
-
|
|
180
|
-
logger.debug("Prepared Pydantic AI input (text_length=%d)", len(text_content))
|
|
181
|
-
return text_content
|
|
182
|
-
|
|
183
|
-
async def ainvoke(
|
|
184
|
-
self,
|
|
185
|
-
input: List[AgentMessage],
|
|
186
|
-
session_id: Union[str, None],
|
|
187
|
-
) -> AgentInvocationResponse:
|
|
188
|
-
"""
|
|
189
|
-
Asynchronous invocation method - invokes the Pydantic AI agent and converts the output to List[AgentMessage]
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
input: List[AgentMessage] containing user_message
|
|
193
|
-
session_id: Optional conversation ID for maintaining context
|
|
194
|
-
|
|
195
|
-
Returns:
|
|
196
|
-
AgentInvocationResponse - response containing agent trace, trajectory, and final response.
|
|
197
|
-
"""
|
|
198
|
-
logger.info(
|
|
199
|
-
"Pydantic AI ainvoke called (session_id=%s, input_messages=%d)",
|
|
200
|
-
session_id,
|
|
201
|
-
len(input),
|
|
202
|
-
)
|
|
203
|
-
agent_input = self._prepare_input(input)
|
|
204
|
-
session_id = session_id or "default"
|
|
130
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
131
|
+
"""Invoke Pydantic AI agent and return response."""
|
|
132
|
+
logger.info("Pydantic AI ainvoke called (session_id=%s)", input.session_id)
|
|
133
|
+
agent_input = input.user_message_str()
|
|
134
|
+
session_id = input.session_id or "default"
|
|
205
135
|
|
|
206
136
|
try:
|
|
207
137
|
# Get message history for this session (for multi-turn conversations)
|
|
@@ -226,7 +156,7 @@ class PydanticAIAdapter(BaseAdapter):
|
|
|
226
156
|
agent_input: str,
|
|
227
157
|
session_id: str,
|
|
228
158
|
message_history: Any | None,
|
|
229
|
-
) ->
|
|
159
|
+
) -> InvokeOutput:
|
|
230
160
|
"""Execute ainvoke with tracing enabled."""
|
|
231
161
|
with self.tracer.start_as_current_span("pydantic_invocation") as span:
|
|
232
162
|
trace_id = span.get_span_context().trace_id
|
|
@@ -266,7 +196,7 @@ class PydanticAIAdapter(BaseAdapter):
|
|
|
266
196
|
else:
|
|
267
197
|
logger.warning("No spans exported for Pydantic AI trace_id=%s", trace_id)
|
|
268
198
|
|
|
269
|
-
return
|
|
199
|
+
return InvokeOutput(
|
|
270
200
|
agent_trace=agent_trace,
|
|
271
201
|
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
272
202
|
framework=Framework.PYDANTIC_AI
|
|
@@ -278,7 +208,7 @@ class PydanticAIAdapter(BaseAdapter):
|
|
|
278
208
|
agent_input: str,
|
|
279
209
|
session_id: str,
|
|
280
210
|
message_history: Any | None,
|
|
281
|
-
) ->
|
|
211
|
+
) -> InvokeOutput:
|
|
282
212
|
"""Execute ainvoke without tracing."""
|
|
283
213
|
# Run the agent asynchronously with message history for context
|
|
284
214
|
if message_history:
|
|
@@ -302,6 +232,6 @@ class PydanticAIAdapter(BaseAdapter):
|
|
|
302
232
|
len(agent_trajectory),
|
|
303
233
|
)
|
|
304
234
|
|
|
305
|
-
return
|
|
235
|
+
return InvokeOutput(
|
|
306
236
|
agent_trajectory=agent_trajectory,
|
|
307
237
|
)
|
|
@@ -1,117 +1,56 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import List, Optional
|
|
3
2
|
|
|
4
3
|
from opentelemetry.trace import TracerProvider
|
|
5
4
|
from smolagents import CodeAgent
|
|
6
5
|
|
|
7
6
|
from quraite.adapters.base import BaseAdapter
|
|
7
|
+
from quraite.constants.framework import Framework
|
|
8
8
|
from quraite.logger import get_logger
|
|
9
|
-
from quraite.schema.
|
|
10
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
11
|
-
from quraite.tracing.constants import Framework
|
|
9
|
+
from quraite.schema.invoke import InvokeInput, InvokeOutput
|
|
12
10
|
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
13
11
|
|
|
14
12
|
logger = get_logger(__name__)
|
|
15
13
|
|
|
16
14
|
|
|
17
15
|
class SmolagentsAdapter(BaseAdapter):
|
|
18
|
-
"""
|
|
19
|
-
Smolagents adapter wrapper that converts any Smolagents CodeAgent
|
|
20
|
-
to a standardized callable interface (ainvoke) with tracing support.
|
|
21
|
-
|
|
22
|
-
This class wraps any CodeAgent and provides:
|
|
23
|
-
- Asynchronous invocation via ainvoke()
|
|
24
|
-
- OpenTelemetry tracing integration
|
|
25
|
-
- Required tracing (returns AgentTrace containing spans)
|
|
26
|
-
"""
|
|
16
|
+
"""Smolagents adapter for CodeAgent (requires tracing)."""
|
|
27
17
|
|
|
28
18
|
def __init__(
|
|
29
19
|
self,
|
|
30
20
|
agent: CodeAgent,
|
|
21
|
+
*,
|
|
22
|
+
tracer_provider: TracerProvider | None = None,
|
|
31
23
|
agent_name: str = "Smolagents Agent",
|
|
32
|
-
tracer_provider: Optional[TracerProvider] = None,
|
|
33
24
|
):
|
|
34
25
|
"""
|
|
35
|
-
Initialize
|
|
26
|
+
Initialize Smolagents adapter.
|
|
36
27
|
|
|
37
28
|
Args:
|
|
38
|
-
agent:
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
agent: Smolagents CodeAgent instance
|
|
30
|
+
tracer_provider: TracerProvider from setup_tracing() (required)
|
|
31
|
+
agent_name: Agent name for metadata
|
|
41
32
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
self._init_tracing(tracer_provider, required=True)
|
|
33
|
+
if tracer_provider is None:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"Smolagents adapter requires tracing. Use setup_tracing([Framework.SMOLAGENTS]) first."
|
|
36
|
+
)
|
|
47
37
|
|
|
48
38
|
self.agent = agent
|
|
49
39
|
self.agent_name = agent_name
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _prepare_input(self, input: List[AgentMessage]) -> str:
|
|
53
|
-
"""
|
|
54
|
-
Prepare input for Smolagents CodeAgent from List[AgentMessage].
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
input: List[AgentMessage] containing user_message
|
|
58
|
-
|
|
59
|
-
Returns:
|
|
60
|
-
str: User message text
|
|
61
|
-
"""
|
|
62
|
-
logger.debug("Preparing Smolagents input from %d messages", len(input))
|
|
63
|
-
if not input or input[-1].role != "user":
|
|
64
|
-
logger.error("Smolagents input missing user message")
|
|
65
|
-
raise ValueError("No user message found in the input")
|
|
66
|
-
|
|
67
|
-
last_user_message = input[-1]
|
|
68
|
-
if not last_user_message.content:
|
|
69
|
-
logger.error("Smolagents user message missing content")
|
|
70
|
-
raise ValueError("User message has no content")
|
|
40
|
+
self._init_tracer(tracer_provider)
|
|
41
|
+
logger.info("SmolagentsAdapter initialized")
|
|
71
42
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
text_content = content_item.text
|
|
76
|
-
break
|
|
77
|
-
|
|
78
|
-
if not text_content:
|
|
79
|
-
logger.error("Smolagents user message missing text content")
|
|
80
|
-
raise ValueError("No text content found in user message")
|
|
81
|
-
|
|
82
|
-
logger.debug("Prepared Smolagents input (text_length=%d)", len(text_content))
|
|
83
|
-
return text_content
|
|
84
|
-
|
|
85
|
-
async def ainvoke(
|
|
86
|
-
self,
|
|
87
|
-
input: List[AgentMessage],
|
|
88
|
-
session_id: Optional[str] = None,
|
|
89
|
-
) -> AgentInvocationResponse:
|
|
90
|
-
"""
|
|
91
|
-
Asynchronous invocation method - invokes the Smolagents CodeAgent with tracing
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
input: List[AgentMessage] containing user_message
|
|
95
|
-
session_id: Optional conversation ID (for parity with other adapters)
|
|
96
|
-
|
|
97
|
-
Returns:
|
|
98
|
-
AgentTrace - trace with spans captured during invocation
|
|
99
|
-
"""
|
|
100
|
-
_ = session_id # Currently unused but kept for interface compatibility
|
|
101
|
-
logger.info(
|
|
102
|
-
"Smolagents ainvoke called (session_id=%s, input_messages=%d)",
|
|
103
|
-
session_id,
|
|
104
|
-
len(input),
|
|
105
|
-
)
|
|
106
|
-
agent_input = self._prepare_input(input)
|
|
43
|
+
async def ainvoke(self, input: InvokeInput) -> InvokeOutput:
|
|
44
|
+
"""Invoke Smolagents agent and return response with trace."""
|
|
45
|
+
logger.info("Smolagents ainvoke called (session_id=%s)", input.session_id)
|
|
107
46
|
|
|
108
47
|
try:
|
|
109
|
-
return await self._ainvoke_with_tracing(
|
|
48
|
+
return await self._ainvoke_with_tracing(input.user_message_str())
|
|
110
49
|
except Exception as exc:
|
|
111
50
|
logger.exception("Error invoking Smolagents agent")
|
|
112
51
|
raise RuntimeError(f"Error invoking Smolagents agent: {exc}") from exc
|
|
113
52
|
|
|
114
|
-
async def _ainvoke_with_tracing(self, agent_input: str) ->
|
|
53
|
+
async def _ainvoke_with_tracing(self, agent_input: str) -> InvokeOutput:
|
|
115
54
|
"""Execute ainvoke with tracing enabled."""
|
|
116
55
|
with self.tracer.start_as_current_span("smolagents_invocation") as span:
|
|
117
56
|
trace_id = span.get_span_context().trace_id
|
|
@@ -140,7 +79,7 @@ class SmolagentsAdapter(BaseAdapter):
|
|
|
140
79
|
else:
|
|
141
80
|
logger.warning("No spans captured for Smolagents trace_id=%s", trace_id)
|
|
142
81
|
|
|
143
|
-
return
|
|
82
|
+
return InvokeOutput(
|
|
144
83
|
agent_trace=agent_trace,
|
|
145
84
|
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
146
85
|
framework=Framework.SMOLAGENTS
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Framework(str, Enum):
|
|
5
|
+
"""Supported agent frameworks."""
|
|
6
|
+
|
|
7
|
+
DEFAULT = "default"
|
|
8
|
+
PYDANTIC_AI = "pydantic_ai"
|
|
9
|
+
LANGCHAIN = "langchain"
|
|
10
|
+
GOOGLE_ADK = "google_adk"
|
|
11
|
+
OPENAI_AGENTS = "openai_agents"
|
|
12
|
+
AGNO = "agno"
|
|
13
|
+
SMOLAGENTS = "smolagents"
|
|
14
|
+
OPENAI = "openai"
|