Motivation
Currently, theAgentHooks
provide valuable lifecycle events for the start/end of an agent run and for tool execution (on_tool_start
/on_tool_end
). However, developers lack the ability to observe the agent's execution at the language model level.
This PR introduces two new hooks,on_llm_start
andon_llm_end
, to provide this deeper level of observability. This change enables several key use cases:
- Performance Monitoring: Precisely measure the latency of LLM calls.
- Debugging & Logging: Log the exact prompts sent to and raw responses received from the model.
- Implementing Custom Logic: Trigger actions (e.g., updating a UI, saving state) immediately before or after the agent "thinks."
Summary of Changes
src/agents/lifecycle.py
Added two new async methods,on_llm_start
andon_llm_end
, to theAgentHooks
base class, matching the existingon_*_start
/on_*_end
pattern.
src/agents/run.py
Wrapped the call tomodel.get_response(...)
in_get_new_response
with invocations of the new hooks so that they fire immediately before and after each LLM call.
tests/test_agent_llm_hooks.py
Added unit tests (using a mock model and spy hooks) to validate:
- The correct sequence of
on_start → on_llm_start → on_llm_end → on_end
in a chat‑only run. - The correct wrapping of tool execution in a tool‑using run:
on_start → on_llm_start → on_llm_end → on_tool_start → on_tool_end → on_llm_start → on_llm_end → on_end
. - That the agent still runs without error when
agent.hooks
isNone
.
Usage Examples
1. Async Example (awaitable viarun
)
importasynciofromtypingimportAny,Optionalfromdotenvimportload_dotenvfromagents.agentimportAgentfromagents.itemsimportModelResponse,TResponseInputItemfromagents.lifecycleimportAgentHooks,RunContextWrapperfromagents.runimportRunner# Load any OPENAI_API_KEY or other env varsload_dotenv()# --- 1. Define a custom hooks class to track LLM calls ---classLLMTrackerHooks(AgentHooks[Any]):asyncdefon_llm_start(self,context:RunContextWrapper,agent:Agent,system_prompt:Optional[str],input_items:list[TResponseInputItem], )->None:print(f">>> [HOOK] Agent '{agent.name}' is calling the LLM with system prompt:{system_promptor'[none]'}" )asyncdefon_llm_end(self,context:RunContextWrapper,agent:Agent,response:ModelResponse, )->None:ifresponse.usage:print(f">>> [HOOK] LLM call finished. Tokens used:{response.usage.total_tokens}")# --- 2. Create your agent with these hooks ---my_agent=Agent(name="MyMonitoredAgent",instructions="Tell me a joke.",hooks=LLMTrackerHooks(),)# --- 3. Drive it via an async main() ---asyncdefmain():result=awaitRunner.run(my_agent,"Tell me a joke.")print(f"\nAgent output:\n{result.final_output}")if__name__=="__main__":asyncio.run(main())
2. Sync Example (blocking viarun_sync
)
fromtypingimportAny,Optionalfromdotenvimportload_dotenvfromagents.agentimportAgentfromagents.itemsimportModelResponse,TResponseInputItemfromagents.lifecycleimportAgentHooks,RunContextWrapperfromagents.runimportRunner# Load any OPENAI_API_KEY or other env varsload_dotenv()# --- 1. Define a custom hooks class to track LLM calls ---classLLMTrackerHooks(AgentHooks[Any]):asyncdefon_llm_start(self,context:RunContextWrapper,agent:Agent,system_prompt:Optional[str],input_items:list[TResponseInputItem], )->None:print(f">>> [HOOK] Agent '{agent.name}' is calling the LLM with system prompt:{system_promptor'[none]'}" )asyncdefon_llm_end(self,context:RunContextWrapper,agent:Agent,response:ModelResponse, )->None:ifresponse.usage:print(f">>> [HOOK] LLM call finished. Tokens used:{response.usage.total_tokens}")# --- 2. Create your agent with these hooks ---my_agent=Agent(name="MyMonitoredAgent",instructions="Tell me a joke.",hooks=LLMTrackerHooks(),)# --- 3. Drive it via an async main() ---defmain():result=Runner.run_sync(my_agent,"Tell me a joke.")print(f"\nAgent output:\n{result.final_output}")if__name__=="__main__":main()
Note
Streaming support foron_llm_start
andon_llm_end
is not yet implemented. These hooks currently fire only on non‑streamed (batch) LLM calls. Support for streaming invocations will be added in a future release.
Checklist
Uh oh!
There was an error while loading.Please reload this page.
Motivation
Currently, the
AgentHooks
provide valuable lifecycle events for the start/end of an agent run and for tool execution (on_tool_start
/on_tool_end
). However, developers lack the ability to observe the agent's execution at the language model level.This PR introduces two new hooks,
on_llm_start
andon_llm_end
, to provide this deeper level of observability. This change enables several key use cases:Summary of Changes
src/agents/lifecycle.py
Added two new async methods,
on_llm_start
andon_llm_end
, to theAgentHooks
base class, matching the existingon_*_start
/on_*_end
pattern.src/agents/run.py
Wrapped the call to
model.get_response(...)
in_get_new_response
with invocations of the new hooks so that they fire immediately before and after each LLM call.tests/test_agent_llm_hooks.py
Added unit tests (using a mock model and spy hooks) to validate:
on_start → on_llm_start → on_llm_end → on_end
in a chat‑only run.on_start → on_llm_start → on_llm_end → on_tool_start → on_tool_end → on_llm_start → on_llm_end → on_end
.agent.hooks
isNone
.Usage Examples
1. Async Example (awaitable via
run
)2. Sync Example (blocking via
run_sync
)Note
Checklist
ruff
).