Skip to content

Commit a8d5459

Browse files
committed
sync readme snippets with code + clean up formatting
1 parent bcd18fb commit a8d5459

File tree

50 files changed

+372
-372
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+372
-372
lines changed

agents/agentic_loop_tool_call_claude_python/README.md

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Each time through the loop:
4040
- Claude is called with the accumulated conversation history that is made up of the initial user input and any previous assistant responses and tool outputs.
4141
- The Workflow checks if Claude returned any tool calls (content blocks with `type: "tool_use"`).
4242
- If tool calls are present, the assistant's complete response (including all content blocks) is appended to the messages array, then all tools are executed, and their results are added as a user message.
43-
- If no tool has been called, the text response is returned.
43+
- If no tool has been called, the text response is returned.
4444

4545
*File: workflows/agent.py*
4646

@@ -58,14 +58,14 @@ with workflow.unsafe.imports_passed_through():
5858
class AgentWorkflow:
5959
@workflow.run
6060
async def run(self, input: str) -> str:
61-
61+
6262
# Initialize messages list with user input
6363
messages = [{"role": "user", "content": input}]
64+
print(f"\n[User] {input}")
6465

6566
# The agentic loop
6667
while True:
67-
print(80 * "=")
68-
68+
6969
# Consult Claude
7070
result = await workflow.execute_activity(
7171
claude_responses.create,
@@ -81,7 +81,7 @@ class AgentWorkflow:
8181

8282
# Claude returns content blocks - check if any are tool_use
8383
tool_use_blocks = [block for block in result.content if block.type == "tool_use"]
84-
84+
8585
if tool_use_blocks:
8686
# We have tool calls to handle
8787
# First, add the assistant's response to messages
@@ -91,61 +91,52 @@ class AgentWorkflow:
9191
if block.type == "text":
9292
assistant_content.append({"type": "text", "text": block.text})
9393
elif block.type == "tool_use":
94+
print(f"[Agent] Calling tool: {block.name}")
9495
assistant_content.append({
9596
"type": "tool_use",
9697
"id": block.id,
9798
"name": block.name,
9899
"input": block.input
99100
})
100-
101+
101102
messages.append({"role": "assistant", "content": assistant_content})
102-
103+
103104
# Execute all tool calls and collect results
104105
tool_results = []
105106
for block in tool_use_blocks:
106-
print(f"[Agent] Tool call: {block.name}({block.input})")
107-
108107
# Execute the tool
109108
tool_result = await self._execute_tool(block.name, block.input)
110-
111-
print(f"[Agent] Tool result: {tool_result}")
112-
109+
113110
# Add tool result in Claude's expected format
114111
tool_results.append({
115112
"type": "tool_result",
116113
"tool_use_id": block.id,
117114
"content": str(tool_result)
118115
})
119-
120-
# Add tool results as a user message
116+
117+
# Add tool results as a user message. Claude has only two message roles: user and assistant,
118+
# and the user message role is used to send tool results back to Claude; in this case the content
119+
# block includes the tool result.
121120
messages.append({"role": "user", "content": tool_results})
122121
else:
123122
# No tool calls - extract the text response and return
124123
text_blocks = [block for block in result.content if block.type == "text"]
125124
if text_blocks:
126125
response_text = text_blocks[0].text
127-
print(f"[Agent] Final response: {response_text}")
126+
print(f"[Agent] Final response: {response_text}\n")
128127
return response_text
129128
else:
130129
return "No text response from Claude"
131-
```
132-
133-
### Create the tool execution handler
134-
135-
The tool execution handler is invoked by the main agentic loop when Claude has chosen tools. Because the Activity implementation is dynamic, the arguments are passed to the Activity as a dictionary. The Activity invocation is the same as any non-dynamic Activity invocation, passing the name of the Activity, the arguments, and any Activity configurations.
136-
137-
*File: workflows/agent.py*
138130

139-
```python
140131
async def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
141132
"""
142133
Execute a tool dynamically.
143-
134+
144135
Args:
145136
tool_name: Name of the tool to execute
146137
tool_input: Dictionary of input parameters
147138
"""
148-
# Execute dynamic Activity with the tool name and arguments
139+
# Execute dynamic activity with the tool name and arguments
149140
result = await workflow.execute_activity(
150141
tool_name,
151142
tool_input,
@@ -154,6 +145,10 @@ The tool execution handler is invoked by the main agentic loop when Claude has c
154145
return result
155146
```
156147

148+
### The tool execution handler
149+
150+
The `_execute_tool` method is invoked by the main agentic loop when Claude has chosen tools. Because the Activity implementation is dynamic, the arguments are passed to the Activity as a dictionary. The Activity invocation is the same as any non-dynamic Activity invocation, passing the name of the Activity, the arguments, and any Activity configurations.
151+
157152
## Create the Activity for Claude invocations
158153

159154
We create a wrapper for the `create` method of the `AsyncAnthropic` client object. This is a generic Activity that invokes Claude's Messages API.
@@ -217,7 +212,7 @@ async def dynamic_tool_activity(args: Sequence[RawValue]) -> dict:
217212
from tools import get_handler
218213

219214
# the name of the tool to execute - this is passed in via the execute_activity call in the Workflow
220-
tool_name = activity.info().activity_type
215+
tool_name = activity.info().activity_type
221216
tool_args = activity.payload_converter().from_payload(args[0].payload, dict)
222217
activity.logger.info(f"Running dynamic tool '{tool_name}' with args: {tool_args}")
223218

@@ -257,7 +252,7 @@ import json
257252
def claude_tool_from_model(name: str, description: str, model: type[BaseModel] | None) -> dict[str, Any]:
258253
"""
259254
Convert a Pydantic model to Claude's tool format.
260-
255+
261256
Claude's tool format structure:
262257
{
263258
"name": "tool_name",
@@ -280,24 +275,17 @@ def claude_tool_from_model(name: str, description: str, model: type[BaseModel] |
280275
"required": []
281276
}
282277
}
283-
278+
284279
# Get the JSON schema from the Pydantic model
285280
schema = model.model_json_schema()
286-
287-
# Claude expects an input_schema field
281+
282+
# Claude expects an input_schema field instead of parameters
288283
return {
289284
"name": name,
290285
"description": description,
291-
"input_schema": {
292-
"type": "object",
293-
"properties": schema.get("properties", {}),
294-
"required": schema.get("required", [])
295-
}
286+
"input_schema": schema
296287
}
297-
```
298288

299-
This file also holds the system instruction for the agent.
300-
```python
301289
HELPFUL_AGENT_SYSTEM_INSTRUCTIONS = """
302290
You are a helpful agent that can use tools to help the user.
303291
You will be given input from the user and a list of tools to use.
@@ -337,7 +325,7 @@ def get_handler(tool_name: str) -> ToolHandler:
337325

338326
def get_tools() -> list[dict[str, Any]]:
339327
return [
340-
get_weather.WEATHER_ALERTS_TOOL_CLAUDE,
328+
get_weather.WEATHER_ALERTS_TOOL_CLAUDE,
341329
get_location.GET_LOCATION_TOOL_CLAUDE,
342330
get_location.GET_IP_ADDRESS_TOOL_CLAUDE
343331
]
@@ -357,9 +345,9 @@ import httpx
357345
from pydantic import BaseModel, Field
358346
from helpers import tool_helpers
359347

360-
# For the location finder we use Pydantic to create a structure that encapsulates the input parameter
361-
# (an IP address).
362-
# This is used for both the location finding function and to craft the tool definitions that
348+
# For the location finder we use Pydantic to create a structure that encapsulates the input parameter
349+
# (an IP address).
350+
# This is used for both the location finding function and to craft the tool definitions that
363351
# are passed to Claude.
364352
class GetLocationRequest(BaseModel):
365353
ipaddress: str = Field(description="An IP address")

agents/agentic_loop_tool_call_claude_python/activities/tool_invoker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
async def dynamic_tool_activity(args: Sequence[RawValue]) -> dict:
1010
from tools import get_handler
1111

12-
# the name of the tool to execute - this is passed in via the execute_activity call in the workflow
13-
tool_name = activity.info().activity_type
12+
# the name of the tool to execute - this is passed in via the execute_activity call in the Workflow
13+
tool_name = activity.info().activity_type
1414
tool_args = activity.payload_converter().from_payload(args[0].payload, dict)
1515
activity.logger.info(f"Running dynamic tool '{tool_name}' with args: {tool_args}")
1616

agents/agentic_loop_tool_call_claude_python/claude_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@
5858
elif block.type == "tool_use":
5959
print(f"Tool call: {block.name}")
6060
print(f"Arguments: {block.input}")
61-
61+
6262
# Simulate tool execution
6363
result = block.input["left"] + block.input["right"]
6464
print(f"Tool result: {result}")
65-
65+
6666
# Send result back to Claude
6767
print("\n" + "=" * 80)
6868
print("Sending tool result back to Claude...")
69-
69+
7070
final_message = client.messages.create(
7171
model="claude-sonnet-4-20250514",
7272
max_tokens=1024,
@@ -86,7 +86,7 @@
8686
}
8787
],
8888
)
89-
89+
9090
print("\nClaude's final response:")
9191
for final_block in final_message.content:
9292
if final_block.type == "text":

agents/agentic_loop_tool_call_claude_python/helpers/tool_helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
def claude_tool_from_model(name: str, description: str, model: type[BaseModel] | None) -> dict[str, Any]:
66
"""
77
Convert a Pydantic model to Claude's tool format.
8-
8+
99
Claude's tool format structure:
1010
{
1111
"name": "tool_name",
@@ -28,10 +28,10 @@ def claude_tool_from_model(name: str, description: str, model: type[BaseModel] |
2828
"required": []
2929
}
3030
}
31-
31+
3232
# Get the JSON schema from the Pydantic model
3333
schema = model.model_json_schema()
34-
34+
3535
# Claude expects an input_schema field instead of parameters
3636
return {
3737
"name": name,

agents/agentic_loop_tool_call_claude_python/start_workflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ async def main():
1616

1717
query = sys.argv[1] if len(sys.argv) > 1 else "Tell me about recursion"
1818

19-
# Submit the agent workflow for execution
19+
# Submit the agent Workflow for execution
2020
result = await client.execute_workflow(
2121
AgentWorkflow.run,
2222
query,

agents/agentic_loop_tool_call_claude_python/tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def get_tools() -> list[dict[str, Any]]:
209209

210210
# OR use random tools instead
211211
# from .random_stuff import get_random_number, RANDOM_NUMBER_TOOL_CLAUDE
212-
#
212+
#
213213
# def get_tools() -> list[dict[str, Any]]:
214214
# return [RANDOM_NUMBER_TOOL_CLAUDE]
215215
```

agents/agentic_loop_tool_call_claude_python/tools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def get_handler(tool_name: str) -> ToolHandler:
1919

2020
def get_tools() -> list[dict[str, Any]]:
2121
return [
22-
get_weather.WEATHER_ALERTS_TOOL_CLAUDE,
22+
get_weather.WEATHER_ALERTS_TOOL_CLAUDE,
2323
get_location.GET_LOCATION_TOOL_CLAUDE,
2424
get_location.GET_IP_ADDRESS_TOOL_CLAUDE
2525
]

agents/agentic_loop_tool_call_claude_python/tools/get_location.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from pydantic import BaseModel, Field
66
from helpers import tool_helpers
77

8-
# For the location finder we use Pydantic to create a structure that encapsulates the input parameter
9-
# (an IP address).
10-
# This is used for both the location finding function and to craft the tool definitions that
8+
# For the location finder we use Pydantic to create a structure that encapsulates the input parameter
9+
# (an IP address).
10+
# This is used for both the location finding function and to craft the tool definitions that
1111
# are passed to Claude.
1212
class GetLocationRequest(BaseModel):
1313
ipaddress: str = Field(description="An IP address")

agents/agentic_loop_tool_call_claude_python/tools/random_stuff.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ class GenerateRandomTextRequest(BaseModel):
3131

3232
async def generate_random_text(req: GenerateRandomTextRequest) -> str:
3333
"""Generate random Lorem Ipsum text."""
34-
words = ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing",
35-
"elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore",
34+
words = ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing",
35+
"elit", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore",
3636
"et", "dolore", "magna", "aliqua"]
37-
37+
3838
result = []
3939
for _ in range(req.length):
4040
result.append(random.choice(words))
41-
41+
4242
return " ".join(result).capitalize() + "."
4343

agents/agentic_loop_tool_call_claude_python/workflows/agent.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
class AgentWorkflow:
1212
@workflow.run
1313
async def run(self, input: str) -> str:
14-
14+
1515
# Initialize messages list with user input
1616
messages = [{"role": "user", "content": input}]
1717
print(f"\n[User] {input}")
1818

1919
# The agentic loop
2020
while True:
21-
21+
2222
# Consult Claude
2323
result = await workflow.execute_activity(
2424
claude_responses.create,
@@ -34,7 +34,7 @@ async def run(self, input: str) -> str:
3434

3535
# Claude returns content blocks - check if any are tool_use
3636
tool_use_blocks = [block for block in result.content if block.type == "tool_use"]
37-
37+
3838
if tool_use_blocks:
3939
# We have tool calls to handle
4040
# First, add the assistant's response to messages
@@ -51,22 +51,22 @@ async def run(self, input: str) -> str:
5151
"name": block.name,
5252
"input": block.input
5353
})
54-
54+
5555
messages.append({"role": "assistant", "content": assistant_content})
56-
56+
5757
# Execute all tool calls and collect results
5858
tool_results = []
5959
for block in tool_use_blocks:
6060
# Execute the tool
6161
tool_result = await self._execute_tool(block.name, block.input)
62-
62+
6363
# Add tool result in Claude's expected format
6464
tool_results.append({
6565
"type": "tool_result",
6666
"tool_use_id": block.id,
6767
"content": str(tool_result)
6868
})
69-
69+
7070
# Add tool results as a user message. Claude has only two message roles: user and assistant,
7171
# and the user message role is used to send tool results back to Claude; in this case the content
7272
# block includes the tool result.
@@ -84,7 +84,7 @@ async def run(self, input: str) -> str:
8484
async def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
8585
"""
8686
Execute a tool dynamically.
87-
87+
8888
Args:
8989
tool_name: Name of the tool to execute
9090
tool_input: Dictionary of input parameters

0 commit comments

Comments
 (0)