Agent Loop docs
Custom observer
agentloop supports Anthropic, OpenAI, OpenRouter, and Modal/vLLM. These examples use
OpenAI. Install its extra and set the provider credentials:
python -m pip install 'based-models-agentloop[openai-compat]'
export LLM_PROVIDER=openai
export OPENAI_API_KEY='your-key'
Count tokens with a callback
An observer receives events while the agent runs. Subclass NullObserver and override only
the callback you need. on_response() runs after each successful model response:
import os
from agentloop import Agent, NullObserver
from agentloop.provider import ModelResponse
class TokenCounter(NullObserver):
def __init__(self) -> None:
self.input_tokens = 0
self.output_tokens = 0
def on_response(self, response: ModelResponse) -> None:
self.input_tokens += response.usage.input_tokens
self.output_tokens += response.usage.output_tokens
counter = TokenCounter()
with Agent.from_env(os.environ, tools=[], observer=counter) as agent:
answer = agent.run("How many days are in a week? Reply with one digit.")
print("answer:", answer)
print("input tokens:", counter.input_tokens)
print("output tokens:", counter.output_tokens)
Output:
answer: 7
input tokens: 19
output tokens: 11
Passing counter as observer registers it with the agent. After each successful model
call, the agent loop calls counter.on_response(response) automatically; your code does
not call the method directly.
input_tokens and output_tokens are integers in ModelResponse. Other usage fields,
such as cache_read_tokens, may be None when the provider does not report them.
Record JSONL events
For a ready-made event log, use EventTraceObserver. Its writer receives one JSONL line per
event. This example keeps those lines in memory and prints their sequence numbers and names:
import json
import os
from agentloop import Agent, EventTraceObserver
lines = []
trace = EventTraceObserver(lines.append)
with Agent.from_env(os.environ, tools=[], observer=trace) as agent:
agent.run("Reply with exactly ready.")
for line in lines:
record = json.loads(line)
print(json.dumps({"seq": record["seq"], "event": record["event"]}))
Output:
{"seq": 1, "event": "conversation_start"}
{"seq": 2, "event": "request"}
{"seq": 3, "event": "response"}
{"seq": 4, "event": "conversation_end"}
The default trace contains metadata, without prompt or response text. See the event trace guide to write the lines to a file or capture content with a redactor.