using livekit+langchain agents, with langchain.age...
# ask-ai
b
using livekit+langchain agents, with langchain.agents.create_agent, example required.
t
Thanks for reaching out, Pranav. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I see you want an example of using LiveKit together with LangChain’s
langchain.agents.create_agent
. Quick acknowledgement:
langchain.agents.create_agent
is not supported by the LiveKit LangChain integration. LiveKit’s langchain plugin expects a compiled LangGraph (
StateGraph
-> Pregel) wrapped by
LLMAdapter
, so the recommended approach is to build a LangGraph workflow and use that as the LLM inside an
AgentSession
. If that works for you, here’s a minimal Python example showing how to wire a compiled LangGraph into LiveKit using the
livekit-plugins-langchain
pattern. 1) Install the plugin
Copy code
uv add "livekit-agents[langchain]~=1.4"
2) Minimal example (Python)
Copy code
from langgraph.graph import StateGraph
from livekit.agents import AgentSession, Agent
from livekit.plugins import langchain

# 1) Define a simple LangGraph workflow and compile it
def create_workflow():
    # Build a small StateGraph with nodes/edges according to your logic
    graph = StateGraph()
    # ... define nodes and transitions here ...
    # return the compiled Pregel/graph expected by LLMAdapter
    return graph.compile()

# 2) Create an AgentSession using the LLMAdapter with the compiled graph
session = AgentSession(
    llm=langchain.LLMAdapter(
        graph=create_workflow()
    ),
    # add optional media components as needed:
    # stt=..., tts=..., vad=...
)

# 3) Create an Agent that uses this session and attach to a LiveKit room
agent = Agent(session=session)

# 4) Start/attach the agent to a user session or room per your app flow
# (room connection / AgentSession lifecycle is handled by your app)
Notes and important points •
LLMAdapter
expects a compiled LangGraph (Pregel) object — not a zero-shot or react agent produced by
langchain.agents.create_agent
. • The LangGraph approach provides explicit state and multi-step workflows, which match LiveKit AgentSession’s stateful voice/agent model. • This integration is available in Python. If you were specifically looking to use
langchain.agents.create_agent
, tell me more about the agent style you need (zero-shot, tool usage, react-style, tools you want to plug in). I can: • Suggest how to re-express that logic as a LangGraph workflow, or • Show patterns for wrapping tool calls inside LangGraph nodes so you get similar behavior to a LangChain agent. Which direction would you like: convert a
create_agent
workflow into LangGraph, or a deeper example of a LangGraph workflow integrated with LiveKit (including STT/TT S placeholders)? Sources: LangChain integration guide | LiveKit Documentation
b
what if i want to specifically use an react agent?
t
Thanks — great question. You’re asking about using a ReAct-style agent created with
langchain.agents.create_agent
together with LiveKit. I’ll be direct: LiveKit’s LangChain plugin does not directly support agents created with
langchain.agents.create_agent
. However, you can get the same ReAct-style behavior by modeling the thinking/action loop inside a LangGraph workflow and using LiveKit’s
LLMAdapter
to run that workflow inside an
AgentSession
. What that looks like and your options • ReAct agents (zero-shot/react-style agents created with
create_agent
) are not supported directly by the LiveKit langchain plugin. • You can reproduce ReAct behavior by creating LangGraph nodes that: ◦ Inspect the chat/context (the “reasoning” step). ◦ Call external tools (HTTP APIs, search, databases) as action nodes. ◦ Return observations back into the graph and continue reasoning. • Wrap the compiled LangGraph (Pregel) with
langchain.LLMAdapter
and pass it to
AgentSession
. That gives you a stateful agent inside LiveKit that can: ◦ Use STT/TT S/VAD components for voice I/O, ◦ Maintain multi-step state, ◦ Call your tools as nodes in the workflow. If you want, I can provide a concrete example. To tailor it, please tell me: • Do you already have specific tools you call (e.g., a search API, a calendar API, or custom functions)? List them and how you call them. • Do you want the reasoning and tool-call loop to run fully inside the graph, or do you prefer the graph to delegate tool calls to external services your app hosts? • Which language/runtime are you using (you mentioned Python earlier — is that correct)? If you want a quick starting example, I can: 1) Show a small LangGraph that implements a ReAct-like loop with one example tool node (HTTP search). 2) Show how to compile it and wire it to
LLMAdapter
and
AgentSession
. 3) Show how to map LiveKit chat messages into the graph’s inputs. Which would be most helpful for you?
b
so basically put an agent inside a workflow, so that we get the same behaviour as an workflow?
t
Short answer: yes — but with one important distinction. You don’t literally “drop a LangChain ReAct agent object inside a LangGraph workflow.” Instead, you re-create the ReAct reasoning/action loop as nodes in a LangGraph workflow, compile that graph, and run it via LiveKit’s
LLMAdapter
inside an
AgentSession
. That gives you the same observable ReAct behavior (thought → action → observation → thought) while remaining compatible with LiveKit’s stateful agent model. Why that matters • LiveKit’s plugin expects a compiled LangGraph (Pregel) object wrapped by
LLMAdapter
.
langchain.agents.create_agent
objects are not directly supported. • Rebuilding ReAct inside LangGraph preserves the core pattern: ◦ A reasoning node produces the next “thought” or plan. ◦ An action node invokes a tool (HTTP API, search, calendar, DB). ◦ An observation node receives the tool response and feeds it back to reasoning. ◦ The loop repeats until a completion node produces the final response. • Running this as a workflow gives you extra benefits: persistent state across turns, explicit control of retries/timeouts, and easier mapping to STT/TTS and VAD in LiveKit. If you’d like, I can provide a concrete Python example that includes: 1) A minimal LangGraph that implements a ReAct-style loop with one example tool (e.g., HTTP search). 2) How to compile the graph and wrap it with
langchain.LLMAdapter
. 3) How to wire that into
AgentSession
(with placeholders for STT/TT S/VAD). 4) Example node designs for reasoning, action, and observation. Which would you prefer? • A short, minimal example to get started, or • A more complete sample including tool call patterns, error handling, and LiveKit message mapping? Also tell me if you already have a specific tool (search API, calendar, database) you want included in the example.
b
do it, i want to see how it works.
t
Great — I’ll show a concrete, minimal Python example that recreates a ReAct-style (reason → action → observation → reason) loop inside a LangGraph workflow, compiles it, wraps it with LiveKit’s
LLMAdapter
, and puts it inside an
AgentSession
. This demonstrates how to get the same behavior as a ReAct agent created with
langchain.agents.create_agent
, but in a form the LiveKit langchain plugin supports. What this example includes • A small tool: an HTTP “search” function (placeholder). • LangGraph nodes: Reason (generate plan), Action (call tool), Observe (capture result), Decide (stop or continue). • Compile the graph and wrap with
langchain.LLMAdapter
. • Create an
AgentSession
and an
Agent
to run in LiveKit (STT/TTS placeholders included). Prerequisites • Python runtime • Installed plugin:
uv add "livekit-agents[langchain]~=1.4"
• LangGraph & LiveKit plugin libs available in your environment Minimal example (Python)
Copy code
# NOTE: this is a minimal illustrative example. Replace placeholders (LLM, STT, TTS, VAD)
# with your actual implementations and credentials.

from langgraph.graph import StateGraph, Node, compile_graph  # hypothetical API names
from livekit.agents import AgentSession, Agent
from livekit.plugins import langchain as lk_langchain

# 1) Example tool: simple HTTP search (synchronous for example)
def http_search_tool(query: str) -> str:
    # Replace with your real HTTP call (requests, httpx) and parsing
    # Here we just return a stubbed response
    return f"search results for: {query}"

# 2) Build a LangGraph with ReAct-style loop
def build_react_graph():
    graph = StateGraph(name="react_workflow")

    # Reason node: given conversation/history, produce `thought` and `action_instruction`
    def reason_fn(inputs):
        # inputs might include 'user_input' and 'history'
        user_input = inputs.get("user_input", "")
        # Produce an action instruction if query-like, otherwise final answer
        # In real use, this should call LLM to produce structured output (thought + action)
        # Here we use a simple heuristic for illustration
        if "search:" in user_input:
            # instruct action and pass the query
            return {"thought": "I should search the web", "action_instruction": user_input.replace("search:", "").strip(), "done": False}
        else:
            return {"thought": "I can answer directly", "final_answer": f"Direct reply to: {user_input}", "done": True}

    reason_node = Node("reason", func=reason_fn, outputs=["thought", "action_instruction", "final_answer", "done"])

    # Action node: call the tool given action_instruction, produce `observation`
    def action_fn(inputs):
        action_instruction = inputs.get("action_instruction", "")
        if not action_instruction:
            return {"observation": None}
        result = http_search_tool(action_instruction)
        return {"observation": result}

    action_node = Node("action", func=action_fn, inputs=["action_instruction"], outputs=["observation"])

    # Observe/Decide node: incorporate observation and decide next step
    def decide_fn(inputs):
        done = inputs.get("done", False)
        observation = inputs.get("observation", "")
        if done:
            return {"final_output": inputs.get("final_answer")}
        # For demo: use observation to produce a final answer
        final = f"Based on observation: {observation}"
        return {"final_output": final}

    decide_node = Node("decide", func=decide_fn, inputs=["done", "final_answer", "observation"], outputs=["final_output"])

    # Wiring: reason -> action (if not done) -> decide -> maybe loop (simple single loop here)
    graph.add_node(reason_node)
    graph.add_node(action_node)
    graph.add_node(decide_node)

    # Connect nodes: reason -> action_instruction -> action
    graph.connect("reason", "action", output_key="action_instruction", input_key="action_instruction")
    # reason outputs done/final_answer into decide
    graph.connect("reason", "decide", output_key="done", input_key="done")
    graph.connect("reason", "decide", output_key="final_answer", input_key="final_answer")
    # action observation -> decide
    graph.connect("action", "decide", output_key="observation", input_key="observation")

    # In a real looping workflow, you'd loop back to reason with observation if not done.
    # For this minimal example we execute a single reason->action->decide cycle.
    compiled = graph.compile()  # compile to Pregel/compiled graph expected by LLMAdapter
    return compiled

# 3) Wrap compiled LangGraph with LLMAdapter
def create_agent_session():
    compiled_graph = build_react_graph()

    # LLMAdapter wraps the compiled graph so LiveKit can execute it as the "LLM" provider.
    llm_adapter = lk_langchain.LLMAdapter(graph=compiled_graph)

    # Placeholder STT/TTS/VAD: replace with real implementations
    stt = None  # e.g., inference.STT(...)
    tts = None  # e.g., inference.TTS(...)
    vad = None  # e.g., silero.VAD.load()

    session = AgentSession(
        llm=llm_adapter,
        stt=stt,
        tts=tts,
        vad=vad,
    )

    agent = Agent(session=session)
    return agent

# 4) Example usage: feed user input into the workflow and run
if __name__ == "__main__":
    agent = create_agent_session()

    # How your app feeds input depends on your LiveKit integration.
    # For a simple test, simulate invoking the compiled workflow directly:
    compiled = build_react_graph()
    # Most runtimes expose a run/execute method on the compiled graph:
    result = compiled.run(inputs={"user_input": "search: latest tech news"})
    print("Final result:", result.get("final_output"))
Key implementation notes and how this maps to ReAct • Reason node = the LLM step that decides what to do next (thought + action instruction). ◦ In production, this node should call an LLM and return structured fields (thought, action to call, done flag, etc.). • Action node = tool invocation (HTTP search, DB lookup, calendar). ◦ Implement tool calls in action nodes; return observations back to the graph. • Observe/Decide node = merge observations and either finish or loop. ◦ For a full ReAct loop, feed the observation back into the Reason node and iterate until
done == True
. •
LLMAdapter
expects a compiled LangGraph (Pregel) object — you cannot pass a
langchain.agents.create_agent
object directly. • Running inside LiveKit: ◦ Use
AgentSession
to attach STT/TTS and VAD so the workflow can be driven by voice and respond with speech. ◦ Map LiveKit chat/message events to the workflow inputs (e.g.,
user_input
) and stream results back to the room. Next steps I can help with • Turn the minimal example into a full looping ReAct graph (looping Reason after Observe) that runs until a termination condition. • Show a concrete example of invoking an external HTTP tool using
httpx
and retry/error handling in an Action node. • Show how to wire LiveKit event handlers to feed room messages and audio into the workflow and emit transcript/tts back into the room. Which follow-up would you like: implement the full loop with iterations, or add a real HTTP tool and error handling next?