forked from conductor-oss/python-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path02_tools.py
More file actions
108 lines (86 loc) · 3.87 KB
/
Copy path02_tools.py
File metadata and controls
108 lines (86 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# Copyright (c) 2025 Agentspan
# Licensed under the MIT License. See LICENSE file in the project root for details.
"""Tools — multiple tools, async, approval.
Demonstrates:
- Multiple @tool functions
- Approval-required tools (human-in-the-loop)
- How tools become Conductor task definitions
Requirements:
- Conductor server with LLM support
- AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable
- AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable
"""
from conductor.ai.agents import Agent, AgentRuntime, EventType, tool
from settings import settings
@tool
def get_weather(city: str) -> dict:
"""Get current weather for a city."""
weather_data = {
"new york": {"temp": 72, "condition": "Partly Cloudy"},
"san francisco": {"temp": 58, "condition": "Foggy"},
"miami": {"temp": 85, "condition": "Sunny"},
}
data = weather_data.get(city.lower(), {"temp": 70, "condition": "Clear"})
return {"city": city, "temperature_f": data["temp"], "condition": data["condition"]}
@tool
def calculate(expression: str) -> dict:
"""Evaluate a math expression."""
import math
safe_builtins = {
"abs": abs, "round": round, "min": min, "max": max,
"sqrt": math.sqrt, "pow": pow, "pi": math.pi, "e": math.e,
}
try:
result = eval(expression, {"__builtins__": {}}, safe_builtins)
return {"expression": expression, "result": result}
except Exception as e:
return {"expression": expression, "error": str(e)}
@tool(approval_required=True, timeout_seconds=60)
def send_email(to: str, subject: str, body: str) -> dict:
"""Send an email."""
# In production, this would actually send an email
return {"status": "sent", "to": to, "subject": subject}
agent = Agent(
name="tool_demo_agent",
model=settings.llm_model,
tools=[get_weather, calculate, send_email],
instructions="You are a helpful assistant with access to weather, calculator, and email tools.",
)
if __name__ == "__main__":
with AgentRuntime() as runtime:
handle = runtime.start(agent, "send email to developer@orkes.io with current weather details in SF")
print(f"Started: {handle.execution_id}\n")
for event in handle.stream():
if event.type == EventType.THINKING:
print(f" [thinking] {event.content}")
elif event.type == EventType.TOOL_CALL:
print(f" [tool_call] {event.tool_name}({event.args})")
elif event.type == EventType.TOOL_RESULT:
print(f" [tool_result] {event.tool_name} -> {str(event.result)[:100]}")
elif event.type == EventType.WAITING:
status = handle.get_status()
pt = status.pending_tool or {}
schema = pt.get("response_schema", {})
props = schema.get("properties", {})
print("\n--- Human input required ---")
response = {}
for field, fs in props.items():
desc = fs.get("description") or fs.get("title", field)
if fs.get("type") == "boolean":
val = input(f" {desc} (y/n): ").strip().lower()
response[field] = val in ("y", "yes")
else:
response[field] = input(f" {desc}: ").strip()
handle.respond(response)
print()
elif event.type == EventType.DONE:
print(f"\nDone: {event.output}")
# Non-interactive alternative (no HITL, will block on human tasks):
# result = runtime.run(agent, "What is the weather in San Francisco?")
# result.print_result()
# Production pattern:
# 1. Deploy once during CI/CD:
# runtime.deploy(agent)
#
# 2. In a separate long-lived worker process:
# runtime.serve(agent)