quraite 0.0.1__py3-none-any.whl → 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.
- quraite/__init__.py +3 -3
- quraite/adapters/__init__.py +134 -134
- quraite/adapters/agno_adapter.py +159 -159
- quraite/adapters/base.py +123 -123
- quraite/adapters/bedrock_agents_adapter.py +343 -343
- quraite/adapters/flowise_adapter.py +275 -275
- quraite/adapters/google_adk_adapter.py +209 -209
- quraite/adapters/http_adapter.py +239 -239
- quraite/adapters/langflow_adapter.py +192 -192
- quraite/adapters/langgraph_adapter.py +304 -304
- quraite/adapters/langgraph_server_adapter.py +252 -252
- quraite/adapters/n8n_adapter.py +220 -220
- quraite/adapters/openai_agents_adapter.py +269 -269
- quraite/adapters/pydantic_ai_adapter.py +312 -312
- quraite/adapters/smolagents_adapter.py +152 -152
- quraite/logger.py +61 -62
- quraite/schema/message.py +91 -54
- quraite/schema/response.py +16 -16
- quraite/serve/__init__.py +1 -1
- quraite/serve/cloudflared.py +210 -210
- quraite/serve/local_agent.py +360 -360
- quraite/tracing/__init__.py +24 -24
- quraite/tracing/constants.py +16 -16
- quraite/tracing/span_exporter.py +115 -115
- quraite/tracing/span_processor.py +49 -49
- quraite/tracing/tool_extractors.py +290 -290
- quraite/tracing/trace.py +564 -494
- quraite/tracing/types.py +179 -179
- quraite/tracing/utils.py +170 -170
- quraite/utils/json_utils.py +269 -269
- {quraite-0.0.1.dist-info → quraite-0.1.0.dist-info}/METADATA +9 -9
- quraite-0.1.0.dist-info/RECORD +35 -0
- {quraite-0.0.1.dist-info → quraite-0.1.0.dist-info}/WHEEL +1 -1
- 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.0.1.dist-info/RECORD +0 -49
|
@@ -1,312 +1,312 @@
|
|
|
1
|
-
from typing import Any, List, Union
|
|
2
|
-
|
|
3
|
-
from opentelemetry.trace import TracerProvider
|
|
4
|
-
from pydantic_ai import Agent
|
|
5
|
-
from pydantic_ai.messages import (
|
|
6
|
-
BuiltinToolCallPart,
|
|
7
|
-
SystemPromptPart,
|
|
8
|
-
TextPart,
|
|
9
|
-
ToolCallPart,
|
|
10
|
-
ToolReturnPart,
|
|
11
|
-
UserPromptPart,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
from quraite.adapters.base import BaseAdapter
|
|
15
|
-
from quraite.logger import get_logger
|
|
16
|
-
from quraite.schema.message import (
|
|
17
|
-
AgentMessage,
|
|
18
|
-
AssistantMessage,
|
|
19
|
-
MessageContentText,
|
|
20
|
-
ToolCall,
|
|
21
|
-
ToolMessage,
|
|
22
|
-
)
|
|
23
|
-
from quraite.schema.response import AgentInvocationResponse
|
|
24
|
-
from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX, Framework
|
|
25
|
-
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
26
|
-
|
|
27
|
-
logger = get_logger(__name__)
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
"""
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
agent: Agent,
|
|
45
|
-
agent_name: str = "Pydantic AI Agent",
|
|
46
|
-
tracer_provider: TracerProvider | None = None,
|
|
47
|
-
):
|
|
48
|
-
"""
|
|
49
|
-
Initialize with a pre-configured Pydantic AI agent
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
agent: A Pydantic AI Agent instance
|
|
53
|
-
agent_name: Name of the agent for trajectory metadata
|
|
54
|
-
"""
|
|
55
|
-
self.agent = agent
|
|
56
|
-
self.agent_name = agent_name
|
|
57
|
-
# Store session state for conversation context
|
|
58
|
-
self._sessions: dict[str, Any] = {}
|
|
59
|
-
self._init_tracing(tracer_provider, required=False)
|
|
60
|
-
logger.info(
|
|
61
|
-
"PydanticAIAdapter initialized (agent_name=%s, tracing_enabled=%s)",
|
|
62
|
-
agent_name,
|
|
63
|
-
bool(tracer_provider),
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
def _convert_pydantic_ai_messages_to_messages(
|
|
67
|
-
self, messages: List[Any]
|
|
68
|
-
) -> List[AgentMessage]:
|
|
69
|
-
"""
|
|
70
|
-
Convert Pydantic AI ModelMessage objects (with parts) to SDK Message format.
|
|
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] = []
|
|
79
|
-
|
|
80
|
-
for msg in messages:
|
|
81
|
-
if not hasattr(msg, "parts"):
|
|
82
|
-
continue
|
|
83
|
-
|
|
84
|
-
content: List[MessageContentText] = []
|
|
85
|
-
tool_calls_list: List[ToolCall] = []
|
|
86
|
-
|
|
87
|
-
for part in msg.parts:
|
|
88
|
-
if isinstance(part, SystemPromptPart) or isinstance(
|
|
89
|
-
part, UserPromptPart
|
|
90
|
-
):
|
|
91
|
-
continue
|
|
92
|
-
|
|
93
|
-
elif isinstance(part, TextPart):
|
|
94
|
-
if part.content:
|
|
95
|
-
content.append(
|
|
96
|
-
MessageContentText(type="text", text=part.content)
|
|
97
|
-
)
|
|
98
|
-
elif isinstance(part, ToolCallPart) or isinstance(
|
|
99
|
-
part, BuiltinToolCallPart
|
|
100
|
-
):
|
|
101
|
-
tool_call_id = part.tool_call_id
|
|
102
|
-
tool_name = part.tool_name
|
|
103
|
-
args = part.args
|
|
104
|
-
|
|
105
|
-
tool_calls_list.append(
|
|
106
|
-
ToolCall(
|
|
107
|
-
id=tool_call_id,
|
|
108
|
-
name=tool_name,
|
|
109
|
-
arguments=args if isinstance(args, dict) else {},
|
|
110
|
-
)
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
elif isinstance(part, ToolReturnPart):
|
|
114
|
-
if content or tool_calls_list:
|
|
115
|
-
converted_messages.append(
|
|
116
|
-
AssistantMessage(
|
|
117
|
-
content=content if content else None,
|
|
118
|
-
tool_calls=tool_calls_list if tool_calls_list else None,
|
|
119
|
-
)
|
|
120
|
-
)
|
|
121
|
-
content = []
|
|
122
|
-
tool_calls_list = []
|
|
123
|
-
|
|
124
|
-
tool_call_id = part.tool_call_id
|
|
125
|
-
|
|
126
|
-
converted_messages.append(
|
|
127
|
-
ToolMessage(
|
|
128
|
-
tool_name=part.tool_name,
|
|
129
|
-
tool_call_id=tool_call_id,
|
|
130
|
-
content=[
|
|
131
|
-
MessageContentText(type="text", text=str(part.content))
|
|
132
|
-
],
|
|
133
|
-
)
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
if content or tool_calls_list:
|
|
137
|
-
converted_messages.append(
|
|
138
|
-
AssistantMessage(
|
|
139
|
-
content=content if content else None,
|
|
140
|
-
tool_calls=tool_calls_list if tool_calls_list else None,
|
|
141
|
-
)
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
logger.info(
|
|
145
|
-
"Converted %d Pydantic AI messages to agent messages",
|
|
146
|
-
len(converted_messages),
|
|
147
|
-
)
|
|
148
|
-
return converted_messages
|
|
149
|
-
|
|
150
|
-
def _prepare_input(self, input: List[AgentMessage]) -> str:
|
|
151
|
-
"""
|
|
152
|
-
Prepare input for Pydantic AI agent from List[AgentMessage].
|
|
153
|
-
|
|
154
|
-
Args:
|
|
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"
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
# Get message history for this session (for multi-turn conversations)
|
|
208
|
-
message_history = self._sessions.get(session_id, None)
|
|
209
|
-
|
|
210
|
-
# Run with or without tracing depending on configuration
|
|
211
|
-
if self.tracer_provider:
|
|
212
|
-
return await self._ainvoke_with_tracing(
|
|
213
|
-
agent_input, session_id, message_history
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
return await self._ainvoke_without_tracing(
|
|
217
|
-
agent_input, session_id, message_history
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
except Exception as e:
|
|
221
|
-
logger.exception("Error invoking Pydantic AI agent")
|
|
222
|
-
raise RuntimeError(f"Error invoking Pydantic AI agent: {e}") from e
|
|
223
|
-
|
|
224
|
-
async def _ainvoke_with_tracing(
|
|
225
|
-
self,
|
|
226
|
-
agent_input: str,
|
|
227
|
-
session_id: str,
|
|
228
|
-
message_history: Any | None,
|
|
229
|
-
) -> AgentInvocationResponse:
|
|
230
|
-
"""Execute ainvoke with tracing enabled."""
|
|
231
|
-
from uuid import uuid4
|
|
232
|
-
|
|
233
|
-
adapter_trace_id = f"{QURAITE_ADAPTER_TRACE_PREFIX}-{uuid4()}"
|
|
234
|
-
logger.debug(
|
|
235
|
-
"Starting Pydantic AI traced invocation (trace_id=%s, session_id=%s)",
|
|
236
|
-
adapter_trace_id,
|
|
237
|
-
session_id,
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
with self.tracer.start_as_current_span(name=adapter_trace_id):
|
|
241
|
-
# Run the agent asynchronously with message history for context
|
|
242
|
-
if message_history:
|
|
243
|
-
result = await self.agent.run(
|
|
244
|
-
agent_input,
|
|
245
|
-
message_history=message_history,
|
|
246
|
-
)
|
|
247
|
-
else:
|
|
248
|
-
result = await self.agent.run(agent_input)
|
|
249
|
-
|
|
250
|
-
# Store the complete updated message history for this session
|
|
251
|
-
self._sessions[session_id] = result.all_messages()
|
|
252
|
-
|
|
253
|
-
trace_readable_spans = self.quraite_span_exporter.get_trace_by_testcase(
|
|
254
|
-
adapter_trace_id
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
if trace_readable_spans:
|
|
258
|
-
agent_trace = AgentTrace(
|
|
259
|
-
spans=[
|
|
260
|
-
AgentSpan.from_readable_oi_span(span)
|
|
261
|
-
for span in trace_readable_spans
|
|
262
|
-
]
|
|
263
|
-
)
|
|
264
|
-
logger.info(
|
|
265
|
-
"Pydantic AI trace collected %d spans for trace_id=%s",
|
|
266
|
-
len(trace_readable_spans),
|
|
267
|
-
adapter_trace_id,
|
|
268
|
-
)
|
|
269
|
-
else:
|
|
270
|
-
logger.warning(
|
|
271
|
-
"No spans exported for Pydantic AI trace_id=%s", adapter_trace_id
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
return AgentInvocationResponse(
|
|
275
|
-
agent_trace=agent_trace,
|
|
276
|
-
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
277
|
-
framework=Framework.PYDANTIC_AI
|
|
278
|
-
),
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
async def _ainvoke_without_tracing(
|
|
282
|
-
self,
|
|
283
|
-
agent_input: str,
|
|
284
|
-
session_id: str,
|
|
285
|
-
message_history: Any | None,
|
|
286
|
-
) -> AgentInvocationResponse:
|
|
287
|
-
"""Execute ainvoke without tracing."""
|
|
288
|
-
# Run the agent asynchronously with message history for context
|
|
289
|
-
if message_history:
|
|
290
|
-
result = await self.agent.run(
|
|
291
|
-
agent_input,
|
|
292
|
-
message_history=message_history,
|
|
293
|
-
)
|
|
294
|
-
else:
|
|
295
|
-
result = await self.agent.run(agent_input)
|
|
296
|
-
|
|
297
|
-
# Store the complete updated message history for this session
|
|
298
|
-
self._sessions[session_id] = result.all_messages()
|
|
299
|
-
|
|
300
|
-
# Convert only the NEW messages to SDK Message format
|
|
301
|
-
agent_trajectory = self._convert_pydantic_ai_messages_to_messages(
|
|
302
|
-
result.new_messages()
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
logger.info(
|
|
306
|
-
"Pydantic AI produced %d trajectory messages without tracing",
|
|
307
|
-
len(agent_trajectory),
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
return AgentInvocationResponse(
|
|
311
|
-
agent_trajectory=agent_trajectory,
|
|
312
|
-
)
|
|
1
|
+
from typing import Any, List, Union
|
|
2
|
+
|
|
3
|
+
from opentelemetry.trace import TracerProvider
|
|
4
|
+
from pydantic_ai import Agent
|
|
5
|
+
from pydantic_ai.messages import (
|
|
6
|
+
BuiltinToolCallPart,
|
|
7
|
+
SystemPromptPart,
|
|
8
|
+
TextPart,
|
|
9
|
+
ToolCallPart,
|
|
10
|
+
ToolReturnPart,
|
|
11
|
+
UserPromptPart,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from quraite.adapters.base import BaseAdapter
|
|
15
|
+
from quraite.logger import get_logger
|
|
16
|
+
from quraite.schema.message import (
|
|
17
|
+
AgentMessage,
|
|
18
|
+
AssistantMessage,
|
|
19
|
+
MessageContentText,
|
|
20
|
+
ToolCall,
|
|
21
|
+
ToolMessage,
|
|
22
|
+
)
|
|
23
|
+
from quraite.schema.response import AgentInvocationResponse
|
|
24
|
+
from quraite.tracing.constants import QURAITE_ADAPTER_TRACE_PREFIX, Framework
|
|
25
|
+
from quraite.tracing.trace import AgentSpan, AgentTrace
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
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
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
agent: Agent,
|
|
45
|
+
agent_name: str = "Pydantic AI Agent",
|
|
46
|
+
tracer_provider: TracerProvider | None = None,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Initialize with a pre-configured Pydantic AI agent
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
agent: A Pydantic AI Agent instance
|
|
53
|
+
agent_name: Name of the agent for trajectory metadata
|
|
54
|
+
"""
|
|
55
|
+
self.agent = agent
|
|
56
|
+
self.agent_name = agent_name
|
|
57
|
+
# Store session state for conversation context
|
|
58
|
+
self._sessions: dict[str, Any] = {}
|
|
59
|
+
self._init_tracing(tracer_provider, required=False)
|
|
60
|
+
logger.info(
|
|
61
|
+
"PydanticAIAdapter initialized (agent_name=%s, tracing_enabled=%s)",
|
|
62
|
+
agent_name,
|
|
63
|
+
bool(tracer_provider),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _convert_pydantic_ai_messages_to_messages(
|
|
67
|
+
self, messages: List[Any]
|
|
68
|
+
) -> List[AgentMessage]:
|
|
69
|
+
"""
|
|
70
|
+
Convert Pydantic AI ModelMessage objects (with parts) to SDK Message format.
|
|
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] = []
|
|
79
|
+
|
|
80
|
+
for msg in messages:
|
|
81
|
+
if not hasattr(msg, "parts"):
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
content: List[MessageContentText] = []
|
|
85
|
+
tool_calls_list: List[ToolCall] = []
|
|
86
|
+
|
|
87
|
+
for part in msg.parts:
|
|
88
|
+
if isinstance(part, SystemPromptPart) or isinstance(
|
|
89
|
+
part, UserPromptPart
|
|
90
|
+
):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
elif isinstance(part, TextPart):
|
|
94
|
+
if part.content:
|
|
95
|
+
content.append(
|
|
96
|
+
MessageContentText(type="text", text=part.content)
|
|
97
|
+
)
|
|
98
|
+
elif isinstance(part, ToolCallPart) or isinstance(
|
|
99
|
+
part, BuiltinToolCallPart
|
|
100
|
+
):
|
|
101
|
+
tool_call_id = part.tool_call_id
|
|
102
|
+
tool_name = part.tool_name
|
|
103
|
+
args = part.args
|
|
104
|
+
|
|
105
|
+
tool_calls_list.append(
|
|
106
|
+
ToolCall(
|
|
107
|
+
id=tool_call_id,
|
|
108
|
+
name=tool_name,
|
|
109
|
+
arguments=args if isinstance(args, dict) else {},
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
elif isinstance(part, ToolReturnPart):
|
|
114
|
+
if content or tool_calls_list:
|
|
115
|
+
converted_messages.append(
|
|
116
|
+
AssistantMessage(
|
|
117
|
+
content=content if content else None,
|
|
118
|
+
tool_calls=tool_calls_list if tool_calls_list else None,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
content = []
|
|
122
|
+
tool_calls_list = []
|
|
123
|
+
|
|
124
|
+
tool_call_id = part.tool_call_id
|
|
125
|
+
|
|
126
|
+
converted_messages.append(
|
|
127
|
+
ToolMessage(
|
|
128
|
+
tool_name=part.tool_name,
|
|
129
|
+
tool_call_id=tool_call_id,
|
|
130
|
+
content=[
|
|
131
|
+
MessageContentText(type="text", text=str(part.content))
|
|
132
|
+
],
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if content or tool_calls_list:
|
|
137
|
+
converted_messages.append(
|
|
138
|
+
AssistantMessage(
|
|
139
|
+
content=content if content else None,
|
|
140
|
+
tool_calls=tool_calls_list if tool_calls_list else None,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
logger.info(
|
|
145
|
+
"Converted %d Pydantic AI messages to agent messages",
|
|
146
|
+
len(converted_messages),
|
|
147
|
+
)
|
|
148
|
+
return converted_messages
|
|
149
|
+
|
|
150
|
+
def _prepare_input(self, input: List[AgentMessage]) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Prepare input for Pydantic AI agent from List[AgentMessage].
|
|
153
|
+
|
|
154
|
+
Args:
|
|
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"
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# Get message history for this session (for multi-turn conversations)
|
|
208
|
+
message_history = self._sessions.get(session_id, None)
|
|
209
|
+
|
|
210
|
+
# Run with or without tracing depending on configuration
|
|
211
|
+
if self.tracer_provider:
|
|
212
|
+
return await self._ainvoke_with_tracing(
|
|
213
|
+
agent_input, session_id, message_history
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return await self._ainvoke_without_tracing(
|
|
217
|
+
agent_input, session_id, message_history
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.exception("Error invoking Pydantic AI agent")
|
|
222
|
+
raise RuntimeError(f"Error invoking Pydantic AI agent: {e}") from e
|
|
223
|
+
|
|
224
|
+
async def _ainvoke_with_tracing(
|
|
225
|
+
self,
|
|
226
|
+
agent_input: str,
|
|
227
|
+
session_id: str,
|
|
228
|
+
message_history: Any | None,
|
|
229
|
+
) -> AgentInvocationResponse:
|
|
230
|
+
"""Execute ainvoke with tracing enabled."""
|
|
231
|
+
from uuid import uuid4
|
|
232
|
+
|
|
233
|
+
adapter_trace_id = f"{QURAITE_ADAPTER_TRACE_PREFIX}-{uuid4()}"
|
|
234
|
+
logger.debug(
|
|
235
|
+
"Starting Pydantic AI traced invocation (trace_id=%s, session_id=%s)",
|
|
236
|
+
adapter_trace_id,
|
|
237
|
+
session_id,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
with self.tracer.start_as_current_span(name=adapter_trace_id):
|
|
241
|
+
# Run the agent asynchronously with message history for context
|
|
242
|
+
if message_history:
|
|
243
|
+
result = await self.agent.run(
|
|
244
|
+
agent_input,
|
|
245
|
+
message_history=message_history,
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
result = await self.agent.run(agent_input)
|
|
249
|
+
|
|
250
|
+
# Store the complete updated message history for this session
|
|
251
|
+
self._sessions[session_id] = result.all_messages()
|
|
252
|
+
|
|
253
|
+
trace_readable_spans = self.quraite_span_exporter.get_trace_by_testcase(
|
|
254
|
+
adapter_trace_id
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if trace_readable_spans:
|
|
258
|
+
agent_trace = AgentTrace(
|
|
259
|
+
spans=[
|
|
260
|
+
AgentSpan.from_readable_oi_span(span)
|
|
261
|
+
for span in trace_readable_spans
|
|
262
|
+
]
|
|
263
|
+
)
|
|
264
|
+
logger.info(
|
|
265
|
+
"Pydantic AI trace collected %d spans for trace_id=%s",
|
|
266
|
+
len(trace_readable_spans),
|
|
267
|
+
adapter_trace_id,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
logger.warning(
|
|
271
|
+
"No spans exported for Pydantic AI trace_id=%s", adapter_trace_id
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return AgentInvocationResponse(
|
|
275
|
+
agent_trace=agent_trace,
|
|
276
|
+
agent_trajectory=agent_trace.to_agent_trajectory(
|
|
277
|
+
framework=Framework.PYDANTIC_AI
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
async def _ainvoke_without_tracing(
|
|
282
|
+
self,
|
|
283
|
+
agent_input: str,
|
|
284
|
+
session_id: str,
|
|
285
|
+
message_history: Any | None,
|
|
286
|
+
) -> AgentInvocationResponse:
|
|
287
|
+
"""Execute ainvoke without tracing."""
|
|
288
|
+
# Run the agent asynchronously with message history for context
|
|
289
|
+
if message_history:
|
|
290
|
+
result = await self.agent.run(
|
|
291
|
+
agent_input,
|
|
292
|
+
message_history=message_history,
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
result = await self.agent.run(agent_input)
|
|
296
|
+
|
|
297
|
+
# Store the complete updated message history for this session
|
|
298
|
+
self._sessions[session_id] = result.all_messages()
|
|
299
|
+
|
|
300
|
+
# Convert only the NEW messages to SDK Message format
|
|
301
|
+
agent_trajectory = self._convert_pydantic_ai_messages_to_messages(
|
|
302
|
+
result.new_messages()
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
logger.info(
|
|
306
|
+
"Pydantic AI produced %d trajectory messages without tracing",
|
|
307
|
+
len(agent_trajectory),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return AgentInvocationResponse(
|
|
311
|
+
agent_trajectory=agent_trajectory,
|
|
312
|
+
)
|