Skip to content

Commit ef6f42a

Browse files
authored
Merge pull request #193 from UiPath/feat/agent-framework-model-node-type
feat: infer model name for agent nodes in graph schema
2 parents 675fe21 + b234899 commit ef6f42a

5 files changed

Lines changed: 115 additions & 9 deletions

File tree

packages/uipath-agent-framework/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-agent-framework"
3-
version = "0.0.8"
3+
version = "0.0.9"
44
description = "Python SDK that enables developers to build and deploy Microsoft Agent Framework agents to the UiPath Cloud Platform"
55
readme = "README.md"
66
requires-python = ">=3.11"

packages/uipath-agent-framework/samples/sequential-structured-output/pyproject.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,3 @@ dev = [
1919

2020
[tool.uv]
2121
prerelease = "allow"
22-
23-
[tool.uv.sources]
24-
uipath-dev = { path = "../../../../../uipath-dev-python", editable = true }
25-
uipath-agent-framework = { path = "../../", editable = true }

packages/uipath-agent-framework/src/uipath_agent_framework/runtime/schema.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,23 @@ def _build_workflow_graph(workflow: Workflow) -> UiPathRuntimeGraph:
211211

212212
# Add a node for each executor
213213
for exec_id, executor in executors.items():
214+
node_type = "node"
215+
metadata: dict[str, Any] | None = None
216+
217+
# AgentExecutors that wrap an agent with a chat client are model nodes
218+
if isinstance(executor, AgentExecutor):
219+
model_name = _get_model_name(executor._agent)
220+
if model_name is not None:
221+
node_type = "model"
222+
metadata = {"model_name": model_name}
223+
214224
nodes.append(
215225
UiPathRuntimeNode(
216226
id=exec_id,
217227
name=exec_id,
218-
type="node",
228+
type=node_type,
219229
subgraph=None,
220-
metadata=None,
230+
metadata=metadata,
221231
)
222232
)
223233

@@ -320,6 +330,21 @@ def _add_executor_tool_nodes(
320330
)
321331

322332

333+
def _get_model_name(agent: Any) -> str | None:
334+
"""Extract the model name from an agent's chat client.
335+
336+
Chat clients (OpenAIChatClient, AnthropicClient) store the model
337+
identifier as ``model_id`` on the client instance.
338+
"""
339+
try:
340+
model_id = agent.client.model_id
341+
if isinstance(model_id, str):
342+
return model_id
343+
except AttributeError:
344+
pass
345+
return None
346+
347+
323348
def get_agent_tools(agent: BaseAgent) -> list[Any]:
324349
"""Extract tools list from an Agent Framework agent.
325350

packages/uipath-agent-framework/tests/test_graph.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@
1515
from uipath_agent_framework.runtime.schema import get_agent_graph
1616

1717

18-
def _make_agent(name="test_agent", tools=None) -> BaseAgent:
18+
def _make_agent(name="test_agent", tools=None, model_id=None) -> BaseAgent:
1919
"""Create a mock BaseAgent for testing."""
2020
agent = MagicMock(spec=BaseAgent)
2121
agent.name = name
2222
agent.default_options = {"tools": tools or []}
23+
if model_id is not None:
24+
client = MagicMock()
25+
client.model_id = model_id
26+
agent.client = client
2327
return agent
2428

2529

@@ -477,3 +481,84 @@ def test_workflow_concurrent_pattern(self):
477481
assert ("topics", "merge") in edge_pairs
478482
assert ("summary", "merge") in edge_pairs
479483
assert ("merge", "__end__") in edge_pairs
484+
485+
def test_agent_executor_with_model_is_model_node(self):
486+
"""AgentExecutor with a chat client becomes a model node."""
487+
inner_agent = _make_agent(name="assistant", model_id="gpt-4.1-mini-2025-04-14")
488+
executors = {
489+
"assistant": _make_executor("assistant", agent=inner_agent),
490+
}
491+
workflow = _make_workflow(
492+
executors=executors,
493+
edge_groups=[],
494+
start_executor_id="assistant",
495+
)
496+
agent = _make_workflow_agent(workflow)
497+
graph = get_agent_graph(agent)
498+
499+
node = next(n for n in graph.nodes if n.id == "assistant")
500+
assert node.type == "model"
501+
assert node.metadata == {"model_name": "gpt-4.1-mini-2025-04-14"}
502+
503+
def test_agent_executor_without_model_is_regular_node(self):
504+
"""AgentExecutor without a chat client stays as a regular node."""
505+
inner_agent = _make_agent(name="assistant")
506+
executors = {
507+
"assistant": _make_executor("assistant", agent=inner_agent),
508+
}
509+
workflow = _make_workflow(
510+
executors=executors,
511+
edge_groups=[],
512+
start_executor_id="assistant",
513+
)
514+
agent = _make_workflow_agent(workflow)
515+
graph = get_agent_graph(agent)
516+
517+
node = next(n for n in graph.nodes if n.id == "assistant")
518+
assert node.type == "node"
519+
assert node.metadata is None
520+
521+
def test_plain_executor_is_regular_node(self):
522+
"""Non-AgentExecutor stays as a regular node."""
523+
executors = {
524+
"step": _make_executor("step"), # no agent
525+
}
526+
workflow = _make_workflow(
527+
executors=executors,
528+
edge_groups=[],
529+
start_executor_id="step",
530+
)
531+
agent = _make_workflow_agent(workflow)
532+
graph = get_agent_graph(agent)
533+
534+
node = next(n for n in graph.nodes if n.id == "step")
535+
assert node.type == "node"
536+
assert node.metadata is None
537+
538+
def test_multi_agent_workflow_with_different_models(self):
539+
"""Multiple agents with different models each get their model name."""
540+
triage_agent = _make_agent(name="triage", model_id="gpt-4.1-2025-04-14")
541+
billing_agent = _make_agent(
542+
name="billing", model_id="anthropic.claude-haiku-4-5-20251001-v1:0"
543+
)
544+
executors = {
545+
"triage": _make_executor("triage", agent=triage_agent),
546+
"billing": _make_executor("billing", agent=billing_agent),
547+
}
548+
workflow = _make_workflow(
549+
executors=executors,
550+
edge_groups=[],
551+
start_executor_id="triage",
552+
)
553+
agent = _make_workflow_agent(workflow)
554+
graph = get_agent_graph(agent)
555+
556+
triage_node = next(n for n in graph.nodes if n.id == "triage")
557+
assert triage_node.type == "model"
558+
assert triage_node.metadata == {"model_name": "gpt-4.1-2025-04-14"}
559+
560+
billing_node = next(n for n in graph.nodes if n.id == "billing")
561+
assert billing_node.type == "model"
562+
assert billing_node.metadata == {
563+
"model_name": "anthropic.claude-haiku-4-5-20251001-v1:0"
564+
}

packages/uipath-agent-framework/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)