Dependency Injection

In shortUse InjectQ with 10xGraph to inject shared services such as checkpointers, stores, callbacks, and app-specific dependencies into graph nodes and tools.

  • 3 min read
  • 14 sections
  • Updated
  • v0.9.2
  • Markdown

Source example: examples/react-injection/react_di.py

What you will build

A ReAct-style graph that uses dependency injection to provide shared services to both the main node and tool functions. The example injects:

  • a custom application dependency
  • the compiled graph checkpointer
  • the callback manager
  • the store abstraction
  • config and state objects passed by the runtime

Prerequisites

  • Python 3.12 or later
  • 10xgraph installed
  • injectq installed
  • A provider key such as GEMINI_API_KEY

Install the extra dependency:

Terminal
pip install injectq

Why use dependency injection in 10xGraph

Dependency injection keeps your node and tool signatures explicit without forcing you to manually create or pass every shared object at each call site.

flowchart LR
    A[InjectQ container] --> B[StateGraph container]
    B --> C[MAIN node]
    B --> D[TOOL node]
    E[compile(checkpointer=...)] --> C
    E --> D
    F[runtime state + config] --> C
    F --> D

Use this pattern when:

  • multiple nodes need the same shared service
  • you want testable, explicit dependencies
  • your tools need access to stateful infrastructure

Step 1 — Create and populate the container

The example gets a singleton InjectQ instance and registers a custom class:

Python
from injectq import Inject, InjectQ

class A:
    pass

container = InjectQ.get_instance()
container.bind_instance(A, A())

Later, the graph is created with that container:

Python
graph = StateGraph(container=container)

That makes the container available across graph execution.

Step 2 — Inject dependencies into a tool

The weather tool uses normal runtime parameters and injected services side by side:

Python
from tenxgraph.storage.checkpointer import InMemoryCheckpointer
from tenxgraph.core.state import AgentState, Message
from tenxgraph.core.state.message_block import ToolResultBlock

def get_weather(
    location: str,
    tool_call_id: str,
    state: AgentState,
    config: dict,
    checkpointer: InMemoryCheckpointer = Inject[InMemoryCheckpointer],
    a: A = Inject[A],
) -> Message:
    res = f"The weather in {location} is sunny"
    return Message.tool_message(
        content=[
            ToolResultBlock(
                call_id=tool_call_id,
                output=res,
                status="completed",
            )
        ],
    )

The key idea is that location, tool_call_id, state, and config come from the graph runtime, while checkpointer and a come from injection.

Step 3 — Inject dependencies into a node

The main node can also receive injected services:

Python
from tenxgraph.storage.store.base_store import BaseStore
from tenxgraph.utils.callbacks import CallbackManager

async def main_agent(
    state: AgentState,
    config: dict,
    callback: CallbackManager = Inject[CallbackManager],
    checkpointer: InMemoryCheckpointer = Inject[InMemoryCheckpointer],
    store: BaseStore | None = Inject[BaseStore],
):
    ...

This is useful for:

  • audit logging
  • callback orchestration
  • long-term storage access
  • shared business services

Runtime and injected data flow

sequenceDiagram
    participant User
    participant Graph
    participant Container as InjectQ
    participant MAIN
    participant TOOL

    User->>Graph: invoke(input, config)
    Graph->>MAIN: state + config
    Container-->>MAIN: callback, checkpointer, store
    MAIN-->>Graph: assistant message with tool call
    Graph->>TOOL: location + tool_call_id + state + config
    Container-->>TOOL: checkpointer + custom dependency A
    TOOL-->>Graph: tool result message

Step 4 — Build the graph

The rest of the graph looks like a standard ReAct loop:

Python
tool_node = ToolNode([get_weather])

graph = StateGraph(container=container)
graph.add_node("MAIN", main_agent)
graph.add_node("TOOL", tool_node)

graph.add_conditional_edges("MAIN", should_use_tools, {"TOOL": "TOOL", END: END})
graph.add_edge("TOOL", "MAIN")
graph.set_entry_point("MAIN")

app = graph.compile(checkpointer=checkpointer)

Compiling with a checkpointer makes that checkpointer available to the runtime, and the example demonstrates injecting it into node code.

Step 5 — Run the example

Python
inp = {"messages": [Message.text_message("Please call the get_weather function for New York City")]}
config = {"thread_id": "12345", "recursion_limit": 10}

res = app.invoke(inp, config=config)

What to expect:

  • the main node prints injected and runtime values
  • the tool receives the injected checkpointer and custom class
  • the message history contains the tool call and the tool result

What is injected and what is not

Value Source
state 10xGraph runtime
config 10xGraph runtime
tool_call_id 10xGraph runtime
checkpointer compiled graph and DI integration
callback DI container / runtime wiring
store DI container / runtime wiring
a custom container binding

Common mistakes

  • Forgetting to install injectq.
  • Creating a container but not passing it to StateGraph(container=container).
  • Assuming injected parameters will appear in the tool schema sent to the model.
  • Treating optional injected services like store as always present.

Key concepts

Concept Details
InjectQ Shared dependency container
Inject[T] Marks a parameter as injectable
StateGraph(container=...) Connects the container to graph execution
runtime parameters Values the graph provides automatically during node and tool execution

What you learned

  • How to register dependencies in InjectQ.
  • How to inject services into both tools and graph nodes.
  • How dependency injection fits into a normal ReAct loop.
  • When DI is helpful for larger production-oriented graphs.

Next step

→ MCP Server to expose tools over the Model Context Protocol.

Last updated for v0.9.2Edit this page on GitHubReport an issue