Callable Tools Factory

Pass a function as `tools` instead of a list.

Pass a function as tools instead of a list. The function is called at the start of each run, so the toolset can vary per user or session.

1"""
2Callable Tools Factory
3======================
4Pass a function as `tools` instead of a list. The function is called
5at the start of each run, so the toolset can vary per user or session.
6
7The factory receives parameters by name via signature inspection:
8- agent: the Agent instance
9- run_context: the current RunContext (has user_id, session_id, etc.)
10- session_state: the current session state dict
11
12Results are cached per user_id (or session_id) by default.
13"""
14
15from kern.agent import Agent
16from kern.models.openai import OpenAIResponses
17from kern.run import RunContext
18
19# ---------------------------------------------------------------------------
20# Tools
21# ---------------------------------------------------------------------------
22
23
24def search_web(query: str) -> str:
25 """Search the web for information."""
26 return f"Search results for: {query}"
27
28
29def search_internal_docs(query: str) -> str:
30 """Search internal documentation (admin only)."""
31 return f"Internal doc results for: {query}"
32
33
34def get_account_balance(account_id: str) -> str:
35 """Get account balance (finance only)."""
36 return f"Balance for {account_id}: $42,000"
37
38
39# ---------------------------------------------------------------------------
40# Callable Factory
41# ---------------------------------------------------------------------------
42
43
44def tools_for_user(run_context: RunContext):
45 """Return different tools based on the user's role stored in session_state."""
46 role = (run_context.session_state or {}).get("role", "viewer")
47 print(f"--> Resolving tools for role: {role}")
48
49 base_tools = [search_web]
50 if role == "admin":
51 base_tools.append(search_internal_docs)
52 if role in ("admin", "finance"):
53 base_tools.append(get_account_balance)
54
55 return base_tools
56
57
58# ---------------------------------------------------------------------------
59# Create Agent
60# ---------------------------------------------------------------------------
61agent = Agent(
62 model=OpenAIResponses(id="gpt-5-mini"),
63 tools=tools_for_user,
64 instructions=[
65 "You are a helpful assistant.",
66 "Use the tools available to you to answer the user's question.",
67 ],
68)
69
70
71# ---------------------------------------------------------------------------
72# Run Agent
73# ---------------------------------------------------------------------------
74if __name__ == "__main__":
75 # Run 1: viewer role - only search_web available
76 # Each user_id gets its own cached toolset
77 print("=== Run as viewer ===")
78 agent.print_response(
79 "Search for recent news about AI agents",
80 user_id="viewer_user",
81 session_state={"role": "viewer"},
82 stream=True,
83 )
84
85 # Run 2: admin role - all tools available
86 # Different user_id means the factory is called again with new context
87 print("\n=== Run as admin ===")
88 agent.print_response(
89 "Search internal docs for the deployment guide and check account balance for ACC-001",
90 user_id="admin_user",
91 session_state={"role": "admin"},
92 stream=True,
93 )

Run the Example

1# Clone and setup repo
2git clone https://github.com/kern-ai/kern.git
3cd kern/cookbook/02_agents/04_tools
4
5# Create and activate virtual environment
6./scripts/demo_setup.sh
7source .venvs/demo/bin/activate
8
9python 01_callable_tools.py