Jiki: Architecture Overview
Jiki is designed to bridge the gap between Large Language Models (LLMs) and the real world. While LLMs are powerful text generators, they often lack access to current information, cannot perform specific actions like calculations or API calls, and cannot directly interact with external data sources. Jiki provides the framework to overcome these limitations by enabling LLMs to reliably use "tools" and access "resources".
At its heart, Jiki uses an Orchestrator to manage the entire process. Think of the Orchestrator as the central conductor. When you provide input (like a question or a command), the Orchestrator takes it and prepares it for the LLM. This involves not just sending your query, but also telling the LLM what tools and resources are available to it.
The LLM then processes this information. If it determines that it needs to use a tool (e.g., a calculator, a web search function, or a database lookup tool) to fulfill your request, it signals this intention back to the Orchestrator.
The Orchestrator understands this signal. It then uses a specialized component, the MCP Client (Model Context Protocol Client), to actually execute the requested tool. This client communicates with the tool, which might be running locally or on a separate server, following the rules of the Model Context Protocol (MCP) standard.
Once the tool finishes its job and returns a result, the MCP Client passes this result back to the Orchestrator. The Orchestrator incorporates this result into the ongoing conversation context and sends it back to the LLM.
Finally, the LLM, now equipped with the information or action result from the tool, generates the final response, which the Orchestrator delivers back to you.
This architecture allows Jiki to augment LLMs with external capabilities in a structured and manageable way. The following sections detail the specific components involved and illustrate the flow of information with diagrams and examples.
Visual Architecture
For visual representations of Jiki's architecture, please refer to the following diagrams:
- Architecture Overview Diagram - High-level component relationships
- Tool Call Sequence Diagram - Step-by-step flow of a tool-augmented interaction
- Component Relationships Diagram - Detailed class relationships and interfaces
- Data Flow Diagram - How data moves through the system
Core Components
Jiki()Factory (injiki/__init__.py): The primary user entry point. Creates and configures theJikiOrchestrator,TraceLogger,JikiClient, andLiteLLMModelwith sensible defaults.JikiOrchestrator(injiki/orchestrator.py): The central coordinator. Manages conversation history, builds prompts, interacts with the LLM, intercepts tool calls, and dispatches them to theIMCPClient.IMCPClientProtocol (injiki/tool_client.py): Defines the interface for communicating with an MCP tool server (discovery, execution, resources, roots).JikiClient(injiki/mcp_client.py): The default concrete implementation ofIMCPClient, usingfastmcpfor communication.BaseMCPClient(injiki/mcp_client.py): Abstract base class for creating custom MCP client implementations.
LiteLLMModel(injiki/models/litellm.py): Wrapper around LiteLLM, providing a consistent interface for interacting with various LLM providers (streaming, function/tool calling interception).IPromptBuilderProtocol (injiki/prompts/prompt_builder.py): Defines how system prompts (including tool schemas) are constructed.DefaultPromptBuilder: Standard implementation.
TraceLogger(injiki/logging.py): Handles logging of interaction events and complete traces.- Utilities (in
jiki/utils/): Helper functions for parsing, validation, context management, etc.
See the Component Relationships Diagram for a visual representation of how these components interact.
Interaction Flow (Single Turn)
Let's trace a user query like "What is 5 * 7?" assuming the calculator tool is available via an MCP server.
- User Input: The user provides the query to
orchestrator.process("What is 5 * 7?")(whereorchestratorwas likely created byJiki()). - Prompt Construction:
JikiOrchestratoruses itsIPromptBuilderto construct the prompt sent to the LLM. This includes:- System instructions.
- Available tool schemas (obtained earlier from
IMCPClient.discover_tools()). - Current conversation history (trimmed to fit context window).
- The latest user query.
- LLM Call:
JikiOrchestratorcallsLiteLLMModel.generate_and_intercept(), passing the constructed prompt and a callback to handle tool calls. - LLM Reasoning & Tool Call Emission: The LLM processes the prompt. Recognizing the need for calculation, it streams back text including a tool call block:
Okay, I can calculate that. <mcp_tool_call tool_name="calculator">{"expression": "5 * 7"}</mcp_tool_call> - Tool Call Interception:
generate_and_intercept()detects the<mcp_tool_call>tag. It pauses streaming back to the orchestrator and invokes the callback provided in step 3. - Parsing & Validation: Inside the callback (within
JikiOrchestrator), the content between the tags is parsed (parse_tool_call_content) and validated (validate_tool_call) against the known schema for thecalculatortool. - Tool Execution:
JikiOrchestratorcallsIMCPClient.execute_tool_call(tool_name="calculator", arguments={"expression": "5 * 7"}).JikiClient(the default implementation) sends the corresponding JSON-RPC request (tools/call) to the MCP server viafastmcp.- The MCP server (e.g.,
calculator_server.py) executes the tool function. - The server sends the result (
35) back via JSON-RPC. JikiClientreceives the result and returns it to the orchestrator.
- Result Injection: The orchestrator formats the tool result (e.g., as an XML block
<mcp_tool_result tool_name="calculator">35</mcp_tool_result>) and adds it to the conversation history/prompt context. - LLM Continues:
generate_and_intercept()resumes streaming, now providing the updated context (including the tool result) back to the LLM. - Final Response Generation: The LLM uses the tool result to formulate the final answer (e.g., "5 * 7 is 35.") and streams it back.
- Output Cleaning: The final streamed response is cleaned (
clean_output) to remove any remaining partial tags or artifacts. - Return Result: The orchestrator returns the final cleaned response to the user.
- Logging & Trace Export: If tracing is enabled (
Jiki(trace=True)),TraceLoggerrecords:- User input and system prompts
<mcp_tool_call>and<mcp_tool_result>blocks- Any
<Assistant_Thought>segments - Server-side log entries captured under
mcp_tracesAll of these combined into a single trace dictionary for export.
The complete sequence is illustrated in the Tool Call Sequence Diagram.
Key Design Principles
- Protocol-Driven: Core components interact via defined interfaces (
IMCPClient,IPromptBuilder), allowing easy extension or replacement. - Abstraction: High-level functions like
Jiki()andJikiOrchestrator.process()hide the complexity of MCP communication, prompt engineering, and tool call handling. - Composability: Components like loggers, clients, and prompt builders can be mixed and matched (especially when constructing
JikiOrchestratormanually). - Leverage Existing Standards: Uses MCP for tool interaction and LiteLLM for broad model support.
The Data Flow Diagram illustrates how these principles work together in practice.
Architecture Overview
This document fulfills Phase 1 (items 1-3) of our refactor plan by providing:
- Module Inventory: A table listing each module, its path, responsibility, and key dependencies.
- Cross-Cutting Concerns: Identification of logging/tracing, serialization, and error-handling patterns.
- Existing Workflows: An overview of tool discovery, execution flow, prompt generation, and resource access.
1. Module Inventory
| Module | Path | Responsibility | Dependencies |
|---|---|---|---|
| JikiOrchestrator | jiki/orchestrator.py |
Core engine handling context, tool calls, streaming | IPromptBuilder, IMCPClient, utils |
| JikiClient | jiki/mcp_client.py |
Full MCP client (discovery, invoke, resources, etc.) | BaseMCPClient, fastmcp, logging, serialization |
| BaseMCPClient | jiki/mcp_client.py |
Abstract base for MCP clients (RPC structure) | abc, JSON |
| IToolClient, IMCPClient | jiki/tool_client.py |
Protocols for tool/resource client interfaces | IResourceManager |
| DefaultPromptBuilder | jiki/prompts/prompt_builder.py |
Build prompts for tools, resources, and user input | None |
| IResourceManager | jiki/resources/resource_manager.py |
Protocol for listing/reading MCP resources | None |
| ITransport, factory | jiki/transports/factory.py |
Transport interface and factory for stdio/SSE | fastmcp.client.transports |
| SamplerConfig, ISamplerConfig | jiki/sampling.py |
LLM sampling parameters (temperature, top_p, etc.) | None |
| Logging utilities | jiki/logging.py, jiki/utils/logging.py |
Structured events and complete traces | os, datetime, json |
| Serialization helpers | jiki/serialization/helpers.py |
JSON serializer default and helper method attachers | json, datetime, Pydantic hooks |
| CLI frontends | jiki/cli.py, tools.json |
Command-line entrypoints and argument parsing | argparse, os, json |
| Utilities | jiki/utils/ |
Context trimming, parsing, streaming, token counting | Various; see individual modules |
| Models | jiki/models/ |
LLM model wrappers and response types | Pydantic, LiteLLM |
The Architecture Overview Diagram provides a visual representation of these components.
2. Cross-Cutting Concerns
Logging & Tracing
TraceLogger(jiki/logging.py): records events vialog_eventand full traces withlog_complete_trace.record_conversation_event(jiki/utils/logging.py): invoked by orchestrator to append history and dispatch to logger.
Serialization
json_serializer_default(jiki/serialization/helpers.py): handles dates, bytes, Pydantic models, and fallback torepr()for unknown types.- Tools/results serialization helper in
BaseMCPClient._process_mcp_result: Used byJikiClientto process results.
Error Handling
- Orchestrator (
jiki/orchestrator.py): catches parse/validation/tool call errors, records<mcp_tool_result>blocks. JikiClient: wraps RPC calls (_call_rpc), handshake, discovery, invocation in try/except, raisingRuntimeErrorfor callers.- CLI (
jiki/cli.py): catches and reportsValueError,FileNotFoundError, JSON parse errors, and uncaught exceptions with clean exit codes.
3. Existing Workflows
3.1 Tool Discovery
- Implementation:
JikiClient.discover_tools()performs MCP initialize handshake and uses_call_rpc('tools/list'), converting schemas. - Invocation:
Jiki(auto_discover_tools=True)creates aJikiClientwhich runsdiscover_tools()and suppliestools_configto orchestrator.
3.2 Execution Flow
- User Input:
JikiOrchestrator.process_user_input()constructs messages, optionally including resources. - Streaming: uses
generate_and_intercept(jiki/utils/streaming.py) to stream LLM tokens, intercepting<mcp_tool_call>blocks. - Tool Call Handling:
_handle_tool_call()parses JSON call, validates viavalidate_tool_call, invokesIMCPClient.execute_tool_call, and injects<mcp_tool_result>. - Continuation: streaming resumes until the model emits the full answer.
3.3 Prompt Generation
- Prompt Builder:
DefaultPromptBuilder.build_initial_prompt()(injiki/prompts/prompt_builder.py) composes system prompt with instructions,<mcp_available_tools>,<mcp_available_resources>and user query. - Available Tools Block:
create_available_tools_block()formats tool schemas for the LLM.
3.4 Resource Access
- Listing:
JikiClient.list_resources()calls_call_rpc('resources/list'). - Reading:
JikiClient.read_resource(uri)calls_call_rpc('resources/read'). - Usage: Orchestrator includes resource list (obtained via
IMCPClientinterface, fulfilled byJikiClient) into the first system prompt.
This overview aligns with our refactor plan's Phase 1 deliverables, establishing a clear map of the codebase and its cross-cutting patterns.