Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions examples/02_agent_with_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@
from datetime import date, datetime
from zoneinfo import ZoneInfo

from dotenv import load_dotenv
from pydantic import BaseModel, Field

import workflowai
from workflowai import Model

load_dotenv(override=True)


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

class HistoricalEventInput(BaseModel):
"""Input model for querying historical events."""

query: str = Field(
description="A query about a historical event",
examples=[
Expand All @@ -43,6 +47,7 @@ class HistoricalEventInput(BaseModel):

class HistoricalEventOutput(BaseModel):
"""Output model containing information about a historical event."""

event_date: str = Field(
description="The date of the event in ISO format (YYYY-MM-DD)",
examples=["1969-07-20", "1945-09-02", "1776-07-04"],
Expand Down Expand Up @@ -101,6 +106,16 @@ async def main():
)
print(run)

# Example: Make the same query but limit at a single turn to get the underlying tool call requests
print("\nExample: Latest Mars Landing")
print("-" * 50)
run = await analyze_historical_event.run(
HistoricalEventInput(query="When was the latest Mars landing?"),
max_turns=0,
max_turns_raises=False,
)
print(run)


if __name__ == "__main__":
asyncio.run(main())
23 changes: 17 additions & 6 deletions workflowai/core/domain/run.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from collections.abc import Iterable
from typing import Any, Generic, Optional, Protocol

Expand Down Expand Up @@ -107,12 +108,22 @@ def format_output(self) -> str:
URL: https://workflowai.com/_/agents/agent-1/runs/test-id
"""
# Format the output string
output = [
"\nOutput:",
"=" * 50,
self.output.model_dump_json(indent=2),
"=" * 50,
]
output: list[str] = []
# In case of partial validation, it is possible that the output is an empty model
if dumped_output := self.output.model_dump():
output += [
"\nOutput:",
"=" * 50,
json.dumps(dumped_output, indent=2),
"=" * 50,
]
if self.tool_call_requests:
output += [
"\nTool Call Requests:",
"=" * 50,
json.dumps(self.model_dump(include={"tool_call_requests"})["tool_call_requests"], indent=2),
"=" * 50,
]

# Add run information if available
if self.cost_usd is not None:
Expand Down
130 changes: 82 additions & 48 deletions workflowai/core/domain/run_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Run,
_AgentBase, # pyright: ignore [reportPrivateUsage]
)
from workflowai.core.domain.tool_call import ToolCallRequest
from workflowai.core.domain.version import Version
from workflowai.core.domain.version_properties import VersionProperties

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


# Test that format_output correctly formats:
# 1. The output as a JSON object
# 2. The cost with $ prefix and correct precision
# 3. The latency with 2 decimal places and 's' suffix
# 4. The run URL
@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
def test_format_output_full():
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
duration_seconds=1.23,
cost_usd=0.001,
)

expected = """\nOutput:
class TestRunFormatOutput:
@pytest.fixture(autouse=True)
def mock_app_url(self):
with patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello") as mock:
yield mock

# Test that format_output correctly formats:
# 1. The output as a JSON object
# 2. The cost with $ prefix and correct precision
# 3. The latency with 2 decimal places and 's' suffix
# 4. The run URL

def test_format_output_full(self):
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
duration_seconds=1.23,
cost_usd=0.001,
)

expected = """\nOutput:
==================================================
{
"message": "hello"
Expand All @@ -85,21 +92,19 @@ def test_format_output_full():
Latency: 1.23s
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""

assert run.format_output() == expected
assert run.format_output() == expected

def test_format_output_very_low_cost(self):
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
duration_seconds=1.23,
cost_usd=4.97625e-05,
)

@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
def test_format_output_very_low_cost():
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
duration_seconds=1.23,
cost_usd=4.97625e-05,
)

expected = """\nOutput:
expected = """\nOutput:
==================================================
{
"message": "hello"
Expand All @@ -109,31 +114,60 @@ def test_format_output_very_low_cost():
Latency: 1.23s
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""

assert run.format_output() == expected


# Test that format_output works correctly when cost and latency are not provided:
# 1. The output is still formatted as a JSON object
# 2. No cost or latency lines are included in the output
# 3. The run URL is still included
@patch("workflowai.env.WORKFLOWAI_APP_URL", "https://workflowai.hello")
def test_format_output_no_cost_latency():
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
)

expected = """\nOutput:
assert run.format_output() == expected

# Test that format_output works correctly when cost and latency are not provided:
# 1. The output is still formatted as a JSON object
# 2. No cost or latency lines are included in the output
# 3. The run URL is still included
def test_format_output_no_cost_latency(self):
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput(message="hello"),
)

expected = """\nOutput:
==================================================
{
"message": "hello"
}
==================================================
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""

assert run.format_output() == expected
assert run.format_output() == expected

def test_format_output_tool_call_requests(self):
run = Run[_TestOutput](
id="run-id",
agent_id="agent-id",
schema_id=1,
output=_TestOutput.model_construct(),
tool_call_requests=[
ToolCallRequest(
id="tool-call-id",
name="tool-call-name",
input={"key": "value"},
),
],
)
assert (
run.format_output()
== """\nTool Call Requests:
==================================================
[
{
"id": "tool-call-id",
"name": "tool-call-name",
"input": {
"key": "value"
}
}
]
==================================================
URL: https://workflowai.hello/_/agents/agent-id/runs/run-id"""
)


class TestRunURL:
Expand Down