Skip to content

Commit 1d56523

Browse files
committed
feat: add example about max_turns
1 parent 364088f commit 1d56523

File tree

3 files changed

+114
-54
lines changed

3 files changed

+114
-54
lines changed

examples/02_agent_with_tools.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@
1111
from datetime import date, datetime
1212
from zoneinfo import ZoneInfo
1313

14+
from dotenv import load_dotenv
1415
from pydantic import BaseModel, Field
1516

1617
import workflowai
1718
from workflowai import Model
1819

20+
load_dotenv(override=True)
21+
1922

2023
def get_current_date() -> str:
2124
"""Return today's date in ISO format (YYYY-MM-DD)"""
@@ -31,6 +34,7 @@ def calculate_days_between(date1: str, date2: str) -> int:
3134

3235
class HistoricalEventInput(BaseModel):
3336
"""Input model for querying historical events."""
37+
3438
query: str = Field(
3539
description="A query about a historical event",
3640
examples=[
@@ -43,6 +47,7 @@ class HistoricalEventInput(BaseModel):
4347

4448
class HistoricalEventOutput(BaseModel):
4549
"""Output model containing information about a historical event."""
50+
4651
event_date: str = Field(
4752
description="The date of the event in ISO format (YYYY-MM-DD)",
4853
examples=["1969-07-20", "1945-09-02", "1776-07-04"],
@@ -101,6 +106,16 @@ async def main():
101106
)
102107
print(run)
103108

109+
# Example: Make the same query but limit at a single turn to get the underlying tool call requests
110+
print("\nExample: Latest Mars Landing")
111+
print("-" * 50)
112+
run = await analyze_historical_event.run(
113+
HistoricalEventInput(query="When was the latest Mars landing?"),
114+
max_turns=0,
115+
max_turns_raises=False,
116+
)
117+
print(run)
118+
104119

105120
if __name__ == "__main__":
106121
asyncio.run(main())

workflowai/core/domain/run.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from collections.abc import Iterable
23
from typing import Any, Generic, Optional, Protocol
34

@@ -107,12 +108,22 @@ def format_output(self) -> str:
107108
URL: https://workflowai.com/_/agents/agent-1/runs/test-id
108109
"""
109110
# Format the output string
110-
output = [
111-
"\nOutput:",
112-
"=" * 50,
113-
self.output.model_dump_json(indent=2),
114-
"=" * 50,
115-
]
111+
output: list[str] = []
112+
# In case of partial validation, it is possible that the output is an empty model
113+
if dumped_output := self.output.model_dump():
114+
output += [
115+
"\nOutput:",
116+
"=" * 50,
117+
json.dumps(dumped_output, indent=2),
118+
"=" * 50,
119+
]
120+
if self.tool_call_requests:
121+
output += [
122+
"\nTool Call Requests:",
123+
"=" * 50,
124+
json.dumps(self.model_dump(include={"tool_call_requests"})["tool_call_requests"], indent=2),
125+
"=" * 50,
126+
]
116127

117128
# Add run information if available
118129
if self.cost_usd is not None:

workflowai/core/domain/run_test.py

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Run,
99
_AgentBase, # pyright: ignore [reportPrivateUsage]
1010
)
11+
from workflowai.core.domain.tool_call import ToolCallRequest
1112
from workflowai.core.domain.version import Version
1213
from workflowai.core.domain.version_properties import VersionProperties
1314

@@ -59,23 +60,29 @@ def test_different_agents(self, run1: Run[_TestOutput], run2: Run[_TestOutput]):
5960
assert run1 == run2
6061

6162

62-
# Test that format_output correctly formats:
63-
# 1. The output as a JSON object
64-
# 2. The cost with $ prefix and correct precision
65-
# 3. The latency with 2 decimal places and 's' suffix
66-
# 4. The run URL
67-
@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
68-
def test_format_output_full():
69-
run = Run[_TestOutput](
70-
id="run-id",
71-
agent_id="agent-id",
72-
schema_id=1,
73-
output=_TestOutput(message="hello"),
74-
duration_seconds=1.23,
75-
cost_usd=0.001,
76-
)
77-
78-
expected = """\nOutput:
63+
class TestRunFormatOutput:
64+
@pytest.fixture(autouse=True)
65+
def mock_app_url(self):
66+
with patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello") as mock:
67+
yield mock
68+
69+
# Test that format_output correctly formats:
70+
# 1. The output as a JSON object
71+
# 2. The cost with $ prefix and correct precision
72+
# 3. The latency with 2 decimal places and 's' suffix
73+
# 4. The run URL
74+
75+
def test_format_output_full(self):
76+
run = Run[_TestOutput](
77+
id="run-id",
78+
agent_id="agent-id",
79+
schema_id=1,
80+
output=_TestOutput(message="hello"),
81+
duration_seconds=1.23,
82+
cost_usd=0.001,
83+
)
84+
85+
expected = """\nOutput:
7986
==================================================
8087
{
8188
"message": "hello"
@@ -85,21 +92,19 @@ def test_format_output_full():
8592
Latency: 1.23s
8693
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""
8794

88-
assert run.format_output() == expected
95+
assert run.format_output() == expected
8996

97+
def test_format_output_very_low_cost(self):
98+
run = Run[_TestOutput](
99+
id="run-id",
100+
agent_id="agent-id",
101+
schema_id=1,
102+
output=_TestOutput(message="hello"),
103+
duration_seconds=1.23,
104+
cost_usd=4.97625e-05,
105+
)
90106

91-
@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
92-
def test_format_output_very_low_cost():
93-
run = Run[_TestOutput](
94-
id="run-id",
95-
agent_id="agent-id",
96-
schema_id=1,
97-
output=_TestOutput(message="hello"),
98-
duration_seconds=1.23,
99-
cost_usd=4.97625e-05,
100-
)
101-
102-
expected = """\nOutput:
107+
expected = """\nOutput:
103108
==================================================
104109
{
105110
"message": "hello"
@@ -109,31 +114,60 @@ def test_format_output_very_low_cost():
109114
Latency: 1.23s
110115
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""
111116

112-
assert run.format_output() == expected
113-
114-
115-
# Test that format_output works correctly when cost and latency are not provided:
116-
# 1. The output is still formatted as a JSON object
117-
# 2. No cost or latency lines are included in the output
118-
# 3. The run URL is still included
119-
@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
120-
def test_format_output_no_cost_latency():
121-
run = Run[_TestOutput](
122-
id="run-id",
123-
agent_id="agent-id",
124-
schema_id=1,
125-
output=_TestOutput(message="hello"),
126-
)
127-
128-
expected = """\nOutput:
117+
assert run.format_output() == expected
118+
119+
# Test that format_output works correctly when cost and latency are not provided:
120+
# 1. The output is still formatted as a JSON object
121+
# 2. No cost or latency lines are included in the output
122+
# 3. The run URL is still included
123+
def test_format_output_no_cost_latency(self):
124+
run = Run[_TestOutput](
125+
id="run-id",
126+
agent_id="agent-id",
127+
schema_id=1,
128+
output=_TestOutput(message="hello"),
129+
)
130+
131+
expected = """\nOutput:
129132
==================================================
130133
{
131134
"message": "hello"
132135
}
133136
==================================================
134137
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""
135138

136-
assert run.format_output() == expected
139+
assert run.format_output() == expected
140+
141+
def test_format_output_tool_call_requests(self):
142+
run = Run[_TestOutput](
143+
id="run-id",
144+
agent_id="agent-id",
145+
schema_id=1,
146+
output=_TestOutput.model_construct(),
147+
tool_call_requests=[
148+
ToolCallRequest(
149+
id="tool-call-id",
150+
name="tool-call-name",
151+
input={"key": "value"},
152+
),
153+
],
154+
)
155+
assert (
156+
run.format_output()
157+
== """\nTool Call Requests:
158+
==================================================
159+
[
160+
{
161+
"id": "tool-call-id",
162+
"name": "tool-call-name",
163+
"input": {
164+
"key": "value"
165+
}
166+
}
167+
]
168+
==================================================
169+
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""
170+
)
137171

138172

139173
class TestRunURL:

0 commit comments

Comments
 (0)