Skip to main content

tuner-langchain

LangChain and LangGraph observability for Tuner SDKs.

Captures node transitions, tool calls, and tool results from LangChain and LangGraph agents and feeds them into the Tuner transcript pipeline — giving you full visibility into what your orchestration layer did on every call.


Overview

When users run LangChain or LangGraph as the orchestration layer inside a LiveKit or Pipecat voice agent, tool calls and node transitions happen inside the graph — invisible to the voice framework. This package bridges that gap.

It works by attaching a LangChain callback handler to the graph invocation. The handler captures every node transition and tool call with wall-clock timestamps, and the Tuner SDK mappers inject them into the transcript at flush time — in the correct position between the user message and the bot response.


Installation

Not yet on PyPI. Install locally from the repo:

pip install -e /path/to/tuner-langchain

Once published:

pip install tuner-langchain

Requirements: Python ≥ 3.10, langchain-core >= 1.0, < 2.0


Usage

With LiveKit

from livekit.agents import AgentSession, JobContext, Agent
from livekit.plugins import langchain
from tuner import TunerPlugin

async def entrypoint(ctx: JobContext):
    session = AgentSession(...)

    plugin = TunerPlugin(session, ctx)
    wrapped_graph = plugin.wrap_graph(my_compiled_graph)

    agent = Agent(
        instructions="",
        llm=langchain.LLMAdapter(
            wrapped_graph,
            stream_mode="custom",
            config={"configurable": {"thread_id": thread_id}},
        ),
    )

    await session.start(agent=agent, room=ctx.room)

For plain LangChain (non-graph):

wrapped_chain = plugin.wrap_chain(my_chain)
agent = Agent(
    instructions="",
    llm=langchain.LLMAdapter(
        wrapped_chain,
        stream_mode="messages",
        config={"configurable": {"thread_id": thread_id}},
    ),
)

What gets captured

Node transitions (node_transition)

Every named LangGraph node or LangChain chain step:

Field Description
node_name The node/step name as defined in the graph
start_ms Start time relative to call start
end_ms End time relative to call start
duration_ms Execution time in milliseconds
inputs Node inputs (omitted when empty)
outputs Node outputs (omitted when empty)
error Error message if the node failed
node_instructions System prompt active during this node's LLM call

LangGraph internal nodes (__start__, __end__, compiled graph root) are filtered out automatically.

Tool calls (agent_function + agent_result)

Every tool invocation inside the graph:

Field Description
tool_name Tool name
inputs Raw input string passed to the tool
output Tool result or error message
is_error Whether the tool raised an error
duration_ms Tool execution time in milliseconds
start_ms Invocation time relative to call start

Data Privacy

By default, tuner-langchain forwards the following data to the Tuner ingestion API:

Field Captured by default How to disable
Node instructions node_instructions=False
Tool inputs tool_inputs=False
Tool outputs tool_outputs=False
Node inputs node_inputs=False
Node outputs node_outputs=False

Node instructions are capped at 300 characters. Tool error output is always captured regardless of tool_outputs — errors are not considered sensitive and are required for debugging.

To disable specific fields, pass a CaptureConfig to wrap_graph() or wrap_chain():

from tuner_langchain import CaptureConfig

wrapped_graph = plugin.wrap_graph(
    my_graph,
    capture=CaptureConfig(
        node_instructions=False,
        tool_inputs=False,
        tool_outputs=False,
        node_inputs=False,
        node_outputs=False,
    )
)

How it fits in the transcript

All segments are sorted chronologically by start_ms — the correct execution order:

user message
  node_transition  "intent_classifier"   (start_ms, end_ms, duration_ms)
  node_transition  "booking_node"        (start_ms, end_ms, inputs, outputs)
  agent_function   "get_patient_info"    (start_ms, inputs)
  agent_result     "get_patient_info"    (start_ms, output, duration_ms)
agent text response
user message
  ...

The segment shape is identical to what Tuner produces for non-LangGraph tool calls — same role, same tool object structure, same timing fields. The Tuner API and frontend handle both paths transparently.


Architecture

src/tuner_langchain/
├── __init__.py          public API
├── models.py            NodeTransition, ToolCallEvent, GraphInvocation
├── accumulator.py       TunerAccumulator — stores events per session
├── segment_builder.py   segments_from_invocation() — used by SDK mappers at flush
└── handlers/
    ├── __init__.py
    ├── base.py          TunerBaseHandler — shared tool + chain-end logic
    ├── langgraph.py     TunerLangGraphHandler — filters __start__/__end__
    └── langchain.py     TunerLangChainHandler — filters anonymous wrappers

TunerBaseHandler holds all shared logic. TunerLangGraphHandler and TunerLangChainHandler only override on_chain_start — the one place where the two frameworks differ in how chain names are interpreted.


Public API

from tuner_langchain import (
    # Handlers — pass to graph/chain config
    TunerLangGraphHandler,
    TunerLangChainHandler,

    # Accumulator — one per session, created by wrap_graph() / wrap_chain()
    TunerAccumulator,

    # Segment builder — used internally by SDK mappers at flush time
    segments_from_invocation,

    # Models
    GraphInvocation,
    NodeTransition,
    ToolCallEvent,
)

Development

uv sync
uv run pytest
uv run ruff check .
uv run mypy

Release files for tuner-langchain 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tuner-langchain 0.1.1
File Size Uploaded
tuner_langchain-0.1.1.tar.gz 20.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tuner-langchain 0.1.1
File Interpreter ABI Platform
tuner_langchain-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 38.2 kB

Release files / tuner_langchain-0.1.1.tar.gz

Download URL tuner_langchain-0.1.1.tar.gz
Size 20.5 kB
Tags Source
SHA-256 checksum
How to use checksums
279121ed509d84b19667fb190441254b6a85966b87fd974b7be6c261a1665d73
BLAKE2b-256 checksum
How to use checksums
df5f0c53ebea11254d4794fa0286a53c3f6151db5ca9809ab0fd81f369bf3084
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release files / tuner_langchain-0.1.1-py3-none-any.whl

Download URL tuner_langchain-0.1.1-py3-none-any.whl
Size 17.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c57df2e5c3760599bd2ec4aa5ac68b9782b050824567280fc122e3fa90d9a636
BLAKE2b-256 checksum
How to use checksums
6e7cece66e96b298991dca42fba1e7f01a7e0c9ba6705b47c4e9da6f50cc4efd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.7

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page