Skip to content

Commit 21fddf6

Browse files
cristipufuclaude
andcommitted
fix: use public API guards, fix enum comparison, run ruff format
- Use Agent.is_call_tools_node() / Agent.is_model_request_node() for type narrowing instead of private module imports - Remove all imports from pydantic_ai._agent_graph - Compare phase against UiPathRuntimeStatePhase.STARTED enum directly - Run ruff format on all files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1d16ea4 commit 21fddf6

3 files changed

Lines changed: 22 additions & 30 deletions

File tree

packages/uipath-pydantic-ai/src/uipath_pydantic_ai/runtime/runtime.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
from pydantic import BaseModel
99
from pydantic_ai import Agent, FunctionToolset
10-
from pydantic_ai._agent_graph import CallToolsNode, ModelRequestNode
11-
from pydantic_ai.messages import ModelResponse, ToolReturnPart
10+
from pydantic_ai.messages import ToolReturnPart
1211
from uipath.core.chat.content import (
1312
UiPathConversationContentPartChunkEvent,
1413
UiPathConversationContentPartEndEvent,
@@ -154,12 +153,12 @@ async def stream(
154153
),
155154
)
156155

157-
assert isinstance(next_node, CallToolsNode)
158-
yield UiPathRuntimeStateEvent(
159-
payload=self._model_response_payload(next_node),
160-
node_name=agent_name,
161-
phase=UiPathRuntimeStatePhase.COMPLETED,
162-
)
156+
if Agent.is_call_tools_node(next_node):
157+
yield UiPathRuntimeStateEvent(
158+
payload=self._model_response_payload(next_node),
159+
node_name=agent_name,
160+
phase=UiPathRuntimeStatePhase.COMPLETED,
161+
)
163162
node = next_node
164163

165164
elif Agent.is_call_tools_node(node):
@@ -178,7 +177,7 @@ async def stream(
178177

179178
next_node = await agent_run.next(node)
180179

181-
if tool_calls and isinstance(next_node, ModelRequestNode):
180+
if tool_calls and Agent.is_model_request_node(next_node):
182181
yield UiPathRuntimeStateEvent(
183182
payload=self._tool_results_payload(next_node),
184183
node_name=tools_node_name,
@@ -204,9 +203,7 @@ def _get_timestamp() -> str:
204203
return now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
205204

206205
@staticmethod
207-
def _model_request_payload(
208-
node: ModelRequestNode[Any, Any],
209-
) -> dict[str, Any]:
206+
def _model_request_payload(node: Any) -> dict[str, Any]:
210207
"""Build payload for a ModelRequestNode STARTED event."""
211208
payload: dict[str, Any] = {}
212209
try:
@@ -223,17 +220,15 @@ def _model_request_payload(
223220
return payload
224221

225222
@staticmethod
226-
def _model_response_payload(
227-
next_node: CallToolsNode[Any, Any],
228-
) -> dict[str, Any]:
223+
def _model_response_payload(next_node: Any) -> dict[str, Any]:
229224
"""Build payload for a ModelRequestNode COMPLETED event.
230225
231226
After agent_run.next() the returned node is the CallToolsNode
232227
which carries the model_response with usage data.
233228
"""
234229
payload: dict[str, Any] = {}
235230
try:
236-
response: ModelResponse = next_node.model_response
231+
response = next_node.model_response
237232
if response.model_name:
238233
payload["model_name"] = response.model_name
239234
usage = response.usage
@@ -247,9 +242,7 @@ def _model_response_payload(
247242
return payload
248243

249244
@staticmethod
250-
def _tool_results_payload(
251-
next_node: ModelRequestNode[Any, Any],
252-
) -> dict[str, Any]:
245+
def _tool_results_payload(next_node: Any) -> dict[str, Any]:
253246
"""Build payload for a CallToolsNode COMPLETED event.
254247
255248
After agent_run.next() the returned node is a ModelRequestNode

packages/uipath-pydantic-ai/src/uipath_pydantic_ai/runtime/schema.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ def _conversation_message_item_schema() -> dict[str, Any]:
286286
"inline": {},
287287
},
288288
"required": ["inline"],
289-
}
289+
},
290290
},
291291
"required": ["data"],
292292
},
293-
}
293+
},
294294
},
295295
"required": ["role", "contentParts"],
296296
}

packages/uipath-pydantic-ai/tests/test_runtime.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -721,19 +721,18 @@ def my_tool(ctx, query: str) -> str:
721721
assert len(ends) >= 1
722722

723723
# Text chunks should exist
724-
chunks = [
725-
e
726-
for e in msg_events
727-
if e.content_part and e.content_part.chunk
728-
]
724+
chunks = [e for e in msg_events if e.content_part and e.content_part.chunk]
729725
assert len(chunks) >= 1
730726

731727

732728
@pytest.mark.asyncio
733729
async def test_stream_tool_only_turn_skips_message_events():
734730
"""Model turns that produce only tool calls (no text) should not emit message events."""
735731
from pydantic_ai.models.test import TestModel
736-
from uipath.runtime.events import UiPathRuntimeStateEvent
732+
from uipath.runtime.events import (
733+
UiPathRuntimeStateEvent,
734+
UiPathRuntimeStatePhase,
735+
)
737736

738737
def my_tool(ctx, query: str) -> str:
739738
"""A tool.
@@ -761,9 +760,9 @@ def my_tool(ctx, query: str) -> str:
761760

762761
# Should have multiple model turns via state events (tool turn + final turn)
763762
agent_started = [
764-
e for e in state_events
765-
if e.node_name == "skip_agent"
766-
and e.phase.value == "started"
763+
e
764+
for e in state_events
765+
if e.node_name == "skip_agent" and e.phase == UiPathRuntimeStatePhase.STARTED
767766
]
768767
assert len(agent_started) >= 2 # at least 2 model request turns
769768

0 commit comments

Comments
 (0)