Skip to content

Commit 7fd2ac6

Browse files
Merge pull request anthropics#302 from anthropics/zh/adds-all-cookbooks
Adds all cookbooks for Opus 4.5
2 parents 9e6ca61 + a0b47a0 commit 7fd2ac6

13 files changed

+6493
-3
lines changed

multimodal/crop_tool.ipynb

Lines changed: 771 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ dependencies = [
99
"numpy>=2.3.4",
1010
"pandas>=2.3.3",
1111
"jupyter>=1.1.1",
12+
"rich>=14.2.0",
13+
"python-dotenv>=1.2.1",
1214
"voyageai>=0.3.5",
1315
]
1416

15-
[tool.uv.sources]
16-
1717
[build-system]
1818
requires = ["hatchling"]
1919
build-backend = "hatchling.build"
@@ -41,7 +41,11 @@ indent-style = "space"
4141
line-ending = "auto"
4242

4343
[tool.ruff.lint]
44+
select = ["E", "F", "I", "W", "UP", "S", "B"]
4445
ignore = [
46+
"E501", # line too long
47+
"S101", # assert used (ok in tests)
48+
"S311", # pseudo-random generators ok for non-crypto
4549
"N806", # variable in function should be lowercase (allow for API responses)
4650
]
4751

tool_use/__init__.py

Whitespace-only changes.

tool_use/automatic-context-compaction.ipynb

Lines changed: 1242 additions & 0 deletions
Large diffs are not rendered by default.

tool_use/ptc.ipynb

Lines changed: 1831 additions & 0 deletions
Large diffs are not rendered by default.

tool_use/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
anthropic>=0.18.0
22
python-dotenv>=1.0.0
3-
ipykernel>=6.29.0 # For Jupyter in VSCode
3+
ipykernel>=6.29.0 # For Jupyter in VSCode
4+
rich>=13.0.0
5+
pandas>=2.0.0

tool_use/tool_search_with_embeddings.ipynb

Lines changed: 915 additions & 0 deletions
Large diffs are not rendered by default.

tool_use/utils/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Shared utilities for Claude tool use cookbooks.
3+
4+
This package contains reusable components for creating cookbook demonstrations:
5+
- visualize: Rich terminal visualization for Claude API responses
6+
- team_expense_api: Example mock API for team expense management demonstrations
7+
"""
8+
9+
from .visualize import show_response, visualize
10+
11+
__all__ = ["visualize", "show_response"]

tool_use/utils/customer_service_api.py

Lines changed: 363 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
"""
2+
Customer Service Tools for Claude
3+
Implements tool functions for processing support tickets
4+
"""
5+
6+
import json
7+
from typing import Literal
8+
9+
from anthropic import beta_tool
10+
11+
from .customer_service_api import (
12+
KNOWLEDGE_BASE,
13+
TEAM_ROUTING,
14+
Ticket,
15+
TicketCategory,
16+
TicketGenerator,
17+
TicketPriority,
18+
TicketStatus,
19+
)
20+
21+
# Global state for demo purposes - in production this would be a database
22+
_ticket_queue: list[Ticket] = []
23+
_current_tickets: dict[str, Ticket] = {}
24+
_queue_index = 0
25+
26+
27+
def initialize_ticket_queue(count: int = 25):
28+
"""Initialize the ticket queue with generated tickets"""
29+
global _ticket_queue, _queue_index, _current_tickets
30+
_ticket_queue = TicketGenerator.generate_batch(count)
31+
_queue_index = 0
32+
_current_tickets = {}
33+
34+
35+
def _get_ticket(ticket_id: str) -> Ticket | None:
36+
"""Helper to retrieve a ticket by ID"""
37+
return _current_tickets.get(ticket_id)
38+
39+
40+
def _serialize_ticket(ticket: Ticket) -> str:
41+
"""Convert ticket to JSON string"""
42+
return json.dumps(
43+
{
44+
"id": ticket.id,
45+
"customer_name": ticket.customer_name,
46+
"customer_email": ticket.customer_email,
47+
"subject": ticket.subject,
48+
"description": ticket.description,
49+
"category": ticket.category.value if ticket.category else None,
50+
"priority": ticket.priority.value if ticket.priority else None,
51+
"status": ticket.status.value,
52+
"created_at": ticket.created_at.isoformat() if ticket.created_at else None,
53+
"assigned_team": ticket.assigned_team,
54+
"notes": ticket.notes,
55+
},
56+
indent=2,
57+
)
58+
59+
60+
@beta_tool
61+
def get_next_ticket() -> str:
62+
"""
63+
Get the next unprocessed ticket from the queue.
64+
Returns ticket details as JSON string.
65+
"""
66+
global _queue_index
67+
68+
if _queue_index >= len(_ticket_queue):
69+
return json.dumps(
70+
{
71+
"error": "No more tickets in queue",
72+
"processed": _queue_index,
73+
"total": len(_ticket_queue),
74+
}
75+
)
76+
77+
ticket = _ticket_queue[_queue_index]
78+
_queue_index += 1
79+
_current_tickets[ticket.id] = ticket
80+
81+
return _serialize_ticket(ticket)
82+
83+
84+
@beta_tool
85+
def classify_ticket(
86+
ticket_id: str, category: Literal["billing", "technical", "account", "product", "shipping"]
87+
) -> str:
88+
"""
89+
Classify a ticket into a category.
90+
91+
Args:
92+
ticket_id: The ticket ID
93+
category: The category to assign
94+
95+
Returns:
96+
Confirmation message
97+
"""
98+
ticket = _get_ticket(ticket_id)
99+
if not ticket:
100+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
101+
102+
ticket.category = TicketCategory(category)
103+
104+
return json.dumps(
105+
{
106+
"success": True,
107+
"message": f"Ticket {ticket_id} classified as {category}",
108+
"ticket_id": ticket_id,
109+
}
110+
)
111+
112+
113+
@beta_tool
114+
def search_knowledge_base(category: str, query: str) -> str:
115+
"""
116+
Search the knowledge base for relevant information.
117+
118+
Args:
119+
category: The category to search (billing, technical, account)
120+
query: Keywords to search for
121+
122+
Returns:
123+
Relevant knowledge base articles as JSON
124+
"""
125+
category_lower = category.lower()
126+
127+
if category_lower not in KNOWLEDGE_BASE:
128+
return json.dumps(
129+
{
130+
"error": f"Category '{category}' not found",
131+
"available_categories": list(KNOWLEDGE_BASE.keys()),
132+
}
133+
)
134+
135+
category_kb = KNOWLEDGE_BASE[category_lower]
136+
137+
# Simple keyword search
138+
query_lower = query.lower()
139+
results = {}
140+
141+
for key, value in category_kb.items():
142+
if isinstance(value, dict):
143+
# Search nested dictionaries
144+
for sub_key, sub_value in value.items():
145+
if query_lower in sub_key.lower() or query_lower in str(sub_value).lower():
146+
if key not in results:
147+
results[key] = {}
148+
results[key][sub_key] = sub_value
149+
else:
150+
# Search flat key-value pairs
151+
if query_lower in key.lower() or query_lower in value.lower():
152+
results[key] = value
153+
154+
return json.dumps(
155+
{
156+
"category": category,
157+
"query": query,
158+
"results": results if results else category_kb,
159+
"all_available": category_kb,
160+
},
161+
indent=2,
162+
)
163+
164+
165+
@beta_tool
166+
def set_priority(ticket_id: str, priority: Literal["low", "medium", "high", "urgent"]) -> str:
167+
"""
168+
Set the priority level for a ticket.
169+
170+
Args:
171+
ticket_id: The ticket ID
172+
priority: Priority level
173+
174+
Returns:
175+
Confirmation message
176+
"""
177+
ticket = _get_ticket(ticket_id)
178+
if not ticket:
179+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
180+
181+
old_priority = ticket.priority.value if ticket.priority else "unset"
182+
ticket.priority = TicketPriority(priority)
183+
184+
return json.dumps(
185+
{
186+
"success": True,
187+
"message": f"Ticket {ticket_id} priority updated from {old_priority} to {priority}",
188+
"ticket_id": ticket_id,
189+
"old_priority": old_priority,
190+
"new_priority": priority,
191+
}
192+
)
193+
194+
195+
@beta_tool
196+
def route_to_team(ticket_id: str, team: str) -> str:
197+
"""
198+
Route a ticket to the appropriate support team.
199+
200+
Args:
201+
ticket_id: The ticket ID
202+
team: Team name (billing-team, tech-support, account-services, product-success, logistics-team)
203+
204+
Returns:
205+
Confirmation message
206+
"""
207+
ticket = _get_ticket(ticket_id)
208+
if not ticket:
209+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
210+
211+
valid_teams = list(TEAM_ROUTING.values())
212+
if team not in valid_teams:
213+
return json.dumps(
214+
{
215+
"error": f"Invalid team '{team}'",
216+
"valid_teams": valid_teams,
217+
}
218+
)
219+
220+
old_team = ticket.assigned_team
221+
ticket.assigned_team = team
222+
ticket.status = TicketStatus.OPEN
223+
224+
return json.dumps(
225+
{
226+
"success": True,
227+
"message": f"Ticket {ticket_id} routed to {team}",
228+
"ticket_id": ticket_id,
229+
"old_team": old_team,
230+
"new_team": team,
231+
}
232+
)
233+
234+
235+
@beta_tool
236+
def draft_response(ticket_id: str, response: str) -> str:
237+
"""
238+
Draft a response to the customer.
239+
240+
Args:
241+
ticket_id: The ticket ID
242+
response: The draft response text
243+
244+
Returns:
245+
Confirmation that draft was saved
246+
"""
247+
ticket = _get_ticket(ticket_id)
248+
if not ticket:
249+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
250+
251+
# Store draft in notes with special prefix
252+
draft_note = f"[DRAFT RESPONSE] {response}"
253+
ticket.notes.append(draft_note)
254+
255+
return json.dumps(
256+
{
257+
"success": True,
258+
"message": f"Draft response saved for ticket {ticket_id}",
259+
"ticket_id": ticket_id,
260+
"draft_length": len(response),
261+
}
262+
)
263+
264+
265+
@beta_tool
266+
def add_note(ticket_id: str, note: str) -> str:
267+
"""
268+
Add an internal note to the ticket.
269+
270+
Args:
271+
ticket_id: The ticket ID
272+
note: Internal note for team reference
273+
274+
Returns:
275+
Confirmation message
276+
"""
277+
ticket = _get_ticket(ticket_id)
278+
if not ticket:
279+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
280+
281+
ticket.notes.append(note)
282+
283+
return json.dumps(
284+
{
285+
"success": True,
286+
"message": f"Note added to ticket {ticket_id}",
287+
"ticket_id": ticket_id,
288+
"total_notes": len(ticket.notes),
289+
}
290+
)
291+
292+
293+
@beta_tool
294+
def mark_complete(ticket_id: str) -> str:
295+
"""
296+
Mark ticket as processed and ready for team review.
297+
298+
Args:
299+
ticket_id: The ticket ID
300+
301+
Returns:
302+
Confirmation and summary of ticket processing
303+
"""
304+
ticket = _get_ticket(ticket_id)
305+
if not ticket:
306+
return json.dumps({"error": f"Ticket {ticket_id} not found"})
307+
308+
# Validate ticket is ready for completion
309+
if not ticket.category:
310+
return json.dumps({"error": "Cannot complete ticket without category classification"})
311+
312+
if not ticket.priority:
313+
return json.dumps({"error": "Cannot complete ticket without priority assignment"})
314+
315+
if not ticket.assigned_team:
316+
return json.dumps({"error": "Cannot complete ticket without team routing"})
317+
318+
ticket.status = TicketStatus.RESOLVED
319+
320+
summary = {
321+
"success": True,
322+
"message": f"Ticket {ticket_id} marked complete",
323+
"ticket_id": ticket_id,
324+
"summary": {
325+
"customer": ticket.customer_name,
326+
"subject": ticket.subject,
327+
"category": ticket.category.value,
328+
"priority": ticket.priority.value,
329+
"assigned_team": ticket.assigned_team,
330+
"total_notes": len(ticket.notes),
331+
"status": ticket.status.value,
332+
},
333+
}
334+
335+
return json.dumps(summary, indent=2)
336+
337+
338+
# Helper function for demo/testing
339+
def get_all_tools():
340+
"""Return all tool functions for registration with Anthropic API"""
341+
return [
342+
get_next_ticket,
343+
classify_ticket,
344+
search_knowledge_base,
345+
set_priority,
346+
route_to_team,
347+
draft_response,
348+
add_note,
349+
mark_complete,
350+
]

0 commit comments

Comments
 (0)