Skip to content

Logging & Tracing in Jiki

Jiki provides structured logging of interactions, which is invaluable for debugging, analysis, and potentially fine-tuning models.

Overview

The primary mechanism is the TraceLogger class, which: - Captures events like user input, LLM prompts, tool calls, tool results, and final LLM responses. - Stores these events in memory. - Can persist the traces to disk in JSON or JSON Lines (.jsonl) format.

Each interaction or "turn" typically generates a trace dictionary containing: - conversation_id: A unique ID for the conversation session. - turn_id: A unique ID for the specific turn within the conversation. - events: A list of dictionaries, each representing a step in the turn (e.g., user_input, llm_prompt, tool_call, tool_result, llm_response). - metadata: Optional additional context. - mcp_traces: A list of raw MCP client interactions and server-side log notifications captured during the turn.

Enabling Tracing

Tracing is enabled by default when using the Jiki() factory function (trace=True).

from jiki import Jiki

# Tracing enabled, logs saved to ./interaction_traces/
jiki_instance = Jiki()

# Tracing enabled, logs saved to ./my_custom_traces/
jiki_instance_custom = Jiki(trace=True, trace_dir="my_custom_traces")

# Tracing disabled
jiki_instance_no_trace = Jiki(trace=False)

Accessing Traces Programmatically

If you need to access traces within your application, the JikiOrchestrator (returned by Jiki()) provides methods via attached helpers:

from jiki import Jiki

# Tracing needs to be enabled for logger to be created
jiki_instance = Jiki(trace=True, trace_dir="traces")

# Process some interactions...
jiki_instance.process("Hello!")
jiki_instance.process("What is 1+1 using the calculator?")

# Get all traces from the current session
all_traces = jiki_instance.get_traces() # Returns List[Dict]

# Export traces to a file (optional, run_ui does this on exit)
# jiki_instance.export_traces("session_log.jsonl") 

Automatic Setup via Jiki()

When you use Jiki(trace=True, ...), it automatically creates and configures a TraceLogger instance and passes it to the JikiOrchestrator. Helper methods like get_traces() and export_traces() are then attached to the orchestrator instance for convenience.

Manual Setup (Advanced)

For more control, you can instantiate TraceLogger and JikiOrchestrator manually:

from jiki.logging import TraceLogger
from jiki.orchestrator import JikiOrchestrator
from jiki.models.litellm import LiteLLMModel
from jiki.mcp_client import JikiClient
from jiki.serialization.helpers import _attach_helper_methods

# 1. Create logger
logger = TraceLogger(trace_dir="manual_traces")

# 2. Create other components (model, MCP client, etc.)
model = LiteLLMModel("anthropic/claude-3-haiku-20240307")
mcp_client = JikiClient(connection_info={"type": "stdio", "script_path": "servers/calculator_server.py"}, logger=logger)

# 3. Create orchestrator, passing the logger
orchestrator = JikiOrchestrator(
    model=model,
    mcp_client=mcp_client,
    tools_config=[...], # Load or define tools manually
    logger=logger
)

# 4. (Optional) Attach helper methods for convenience
_attach_helper_methods(orchestrator, logger)

# Now use the orchestrator
# orchestrator.process("...")
# traces = orchestrator.get_traces() 
# orchestrator.export_traces("manual_run.jsonl")

Trace Format Example (.jsonl)

{"conversation_id": "conv_abc", "turn_id": "turn_1", "events": [{"type": "user_input", "content": "What is 5+7?"}, {"type": "llm_prompt", "content": "...", "model": "..."}, {"type": "tool_call", "tool_name": "calculator", "arguments": {"expression": "5+7"}}, {"type": "tool_result", "tool_name": "calculator", "content": "12"}, {"type": "llm_response", "content": "5 + 7 is 12."}], "metadata": {}}
{"conversation_id": "conv_abc", "turn_id": "turn_2", "events": [...]} 

This structured format makes it easy to parse logs and analyze the interaction flow between the user, LLM, and tools.