Files
2026-07-13 12:39:17 +08:00

220 lines
6.2 KiB
Python

import asyncio
import json
import sys
from collections.abc import Mapping
from typing import Annotated, Any
from agents import (
Agent,
ModelSettings,
Runner,
ToolSearchTool,
function_tool,
tool_namespace,
trace,
)
CUSTOMER_PROFILES = {
"customer_42": {
"customer_id": "customer_42",
"full_name": "Avery Chen",
"tier": "enterprise",
}
}
OPEN_ORDERS = {
"customer_42": [
{"order_id": "ord_1042", "status": "awaiting fulfillment"},
{"order_id": "ord_1049", "status": "pending approval"},
]
}
INVOICE_STATUSES = {
"inv_2001": "paid",
}
SHIPPING_ETAS = {
"ZX-123": "2026-03-06 14:00 JST",
}
SHIPPING_CREDIT_BALANCES = {
"customer_42": "$125.00",
}
@function_tool(defer_loading=True)
def get_customer_profile(
customer_id: Annotated[str, "The CRM customer identifier to look up."],
) -> str:
"""Fetch a CRM customer profile."""
return json.dumps(CUSTOMER_PROFILES[customer_id], indent=2)
@function_tool(defer_loading=True)
def list_open_orders(
customer_id: Annotated[str, "The CRM customer identifier to look up."],
) -> str:
"""List open orders for a customer."""
return json.dumps(OPEN_ORDERS.get(customer_id, []), indent=2)
@function_tool(defer_loading=True)
def get_invoice_status(
invoice_id: Annotated[str, "The invoice identifier to look up."],
) -> str:
"""Look up the status of an invoice."""
return INVOICE_STATUSES.get(invoice_id, "unknown")
@function_tool(defer_loading=True)
def get_shipping_eta(
tracking_number: Annotated[str, "The shipment tracking number to look up."],
) -> str:
"""Look up a shipment ETA by tracking number."""
return SHIPPING_ETAS.get(tracking_number, "unavailable")
@function_tool(defer_loading=True)
def get_shipping_credit_balance(
customer_id: Annotated[str, "The customer account identifier to look up."],
) -> str:
"""Look up the available shipping credit balance for a customer."""
return SHIPPING_CREDIT_BALANCES.get(customer_id, "$0.00")
crm_tools = tool_namespace(
name="crm",
description="CRM tools for customer lookups.",
tools=[get_customer_profile, list_open_orders],
)
billing_tools = tool_namespace(
name="billing",
description="Billing tools for invoice lookups.",
tools=[get_invoice_status],
)
namespaced_agent = Agent(
name="Operations assistant",
model="gpt-5.6-sol",
instructions=(
"For customer questions in this example, load the full `crm` namespace with no query "
"filter before calling tools. "
"Do not search `billing` unless the user asks about invoices."
),
model_settings=ModelSettings(parallel_tool_calls=False),
tools=[*crm_tools, *billing_tools, ToolSearchTool()],
)
top_level_agent = Agent(
name="Shipping assistant",
model="gpt-5.6-sol",
instructions=(
"For ETA questions in this example, search `get_shipping_eta` before calling tools. "
"Do not search `get_shipping_credit_balance` unless the user asks about shipping credits."
),
model_settings=ModelSettings(parallel_tool_calls=False),
tools=[get_shipping_eta, get_shipping_credit_balance, ToolSearchTool()],
)
def loaded_paths(result: Any) -> list[str]:
paths: set[str] = set()
for item in result.new_items:
if item.type != "tool_search_output_item":
continue
raw_tools = (
item.raw_item.get("tools")
if isinstance(item.raw_item, Mapping)
else getattr(item.raw_item, "tools", None)
)
if not isinstance(raw_tools, list):
continue
for raw_tool in raw_tools:
tool_payload = (
raw_tool
if isinstance(raw_tool, Mapping)
else (
raw_tool.model_dump(exclude_unset=True)
if callable(getattr(raw_tool, "model_dump", None))
else None
)
)
if not isinstance(tool_payload, Mapping):
continue
tool_type = tool_payload.get("type")
if tool_type == "namespace":
path = tool_payload.get("name")
elif tool_type == "function":
path = tool_payload.get("name")
else:
path = tool_payload.get("server_label")
if isinstance(path, str) and path:
paths.add(path)
return sorted(paths)
def print_result(title: str, result: Any, registered_paths: list[str]) -> None:
loaded = loaded_paths(result)
untouched = [path for path in registered_paths if path not in loaded]
print(f"## {title}")
print("### Final output")
print(result.final_output)
print("\n### Loaded paths")
print(f"- registered: {', '.join(registered_paths)}")
print(f"- loaded: {', '.join(loaded) if loaded else 'none'}")
print(f"- untouched: {', '.join(untouched) if untouched else 'none'}")
print("\n### Relevant items")
for item in result.new_items:
if item.type in {"tool_search_call_item", "tool_search_output_item", "tool_call_item"}:
print(f"- {item.type}: {item.raw_item}")
print()
async def run_namespaced_example() -> None:
result = await Runner.run(
namespaced_agent,
"Look up customer_42 and list their open orders.",
)
print_result(
"Tool search with namespaces",
result,
registered_paths=["crm", "billing"],
)
async def run_top_level_example() -> None:
result = await Runner.run(
top_level_agent,
"Can you get my ETA for tracking number ZX-123?",
)
print_result(
"Tool search with top-level deferred tools",
result,
registered_paths=["get_shipping_eta", "get_shipping_credit_balance"],
)
async def main() -> None:
mode = sys.argv[1] if len(sys.argv) > 1 else "all"
if mode not in {"all", "namespace", "top-level"}:
raise SystemExit(f"Unknown mode: {mode}. Expected one of: all, namespace, top-level.")
with trace("Tool search example"):
if mode in {"all", "namespace"}:
await run_namespaced_example()
if mode in {"all", "top-level"}:
await run_top_level_example()
if __name__ == "__main__":
asyncio.run(main())