Skip to main content

OnTick

The onTick feature provides real-time callbacks during BAML function execution, giving access to internal state, streaming responses, and progress updates. Useful for monitoring, debugging, and accessing intermediate data like "thinking" content.

Basic Usage​

from baml_client import b
from baml_py import baml_py

def on_tick(reason: str, log: baml_py.FunctionLog):
print(f"Tick received: {reason}")
print(f"Function calls: {len(log.calls) if log else 0}")

result = await b.TestFunction("Hello world", baml_options={"on_tick": on_tick})

Examples​

Progress Monitoring​

from baml_client import b
from baml_py import baml_py

def progress_monitor(reason: str, log: baml_py.FunctionLog):
tick_count = getattr(progress_monitor, 'count', 0)
progress_monitor.count = tick_count + 1

print(f"Progress tick #{progress_monitor.count}: {reason}")

if log and log.calls:
latest_call = log.calls[-1]
print(f"Latest call to: {latest_call.client_name}")

result = await b.ExtractResume(
resume_text,
baml_options={"on_tick": progress_monitor}
)

Accessing Streaming "Thinking" Content​

import json
from baml_client import b
from baml_py import baml_py

def extract_thinking(reason: str, log: baml_py.FunctionLog):
thinking_content = ""

if log and log.calls:
last_call = log.calls[-1]

if hasattr(last_call, "sse_responses"):
sse_responses = last_call.sse_responses()
if sse_responses:
for response in sse_responses:
try:
data = json.loads(response.text)
if "delta" in data and "thinking" in data["delta"]:
thinking_content += data["delta"]["thinking"]
except (json.JSONDecodeError, AttributeError):
pass

if thinking_content:
print(f"Thinking content: {thinking_content}")

stream = b.stream.TestThinking(
"Write a story about AI",
baml_options={"on_tick": extract_thinking}
)

async for msg in stream:
pass

result = await stream.get_final_response()

Debugging and Logging​

from baml_client import b
from baml_py import baml_py

def debug_logger(reason: str, log: baml_py.FunctionLog):
print(f"=== DEBUG TICK: {reason} ===")

if log:
print(f"Function: {log.function_name}")
print(f"Log type: {log.log_type}")
print(f"Number of calls: {len(log.calls)}")

if log.usage:
print(f"Input tokens: {log.usage.input_tokens}")
print(f"Output tokens: {log.usage.output_tokens}")

if log.calls:
latest_call = log.calls[-1]
print(f"Latest provider: {latest_call.provider}")
print(f"Latest client: {latest_call.client_name}")

print("=== END DEBUG ===\n")

result = await b.TestFunction("Debug this call", baml_options={"on_tick": debug_logger})

Using with Collectors​

from baml_client import b
from baml_py import baml_py, Collector

def on_tick_with_collector(reason: str, log: baml_py.FunctionLog):
print(f"OnTick fired: {reason}")

collector = Collector("my-collector")

result = await b.TestFunction(
"Hello world",
baml_options={
"on_tick": on_tick_with_collector,
"collector": collector
}
)

print(f"Collector usage: {collector.last.usage}")

Callback Signature​

def on_tick(reason: str, log: baml_py.FunctionLog | None) -> None:
"""
Args:
reason: The reason for the tick (currently always "Unknown")
log: The current function log with call information
"""
pass

Limitations​

warning
  • Synchronous Functions: OnTick is not supported for synchronous function calls
  • Error Isolation: Errors in callbacks do not stop function execution

Best Practices​

  1. Keep Callbacks Light: Should be fast and non-blocking
  2. Handle Errors Gracefully: Always include error handling
  3. Use with Collectors: Combine for comprehensive logging
  4. Monitor Performance: Test performance impact for your use case
  5. Async Only: Only works with async function calls

See Also​