364 lines
12 KiB
Plaintext
364 lines
12 KiB
Plaintext
---
|
|
title: "LangGraph"
|
|
icon: 'diagram-project'
|
|
description: "Build a stateful conversational AI agent with LangGraph and Honcho"
|
|
sidebarTitle: 'LangGraph'
|
|
---
|
|
|
|
Integrate Honcho with LangGraph to build a conversational AI agent that maintains memory across sessions. This guide shows you how to use Honcho's memory layer with LangGraph's orchestration.
|
|
|
|
<Note>
|
|
The full code is available on [GitHub](https://github.com/plastic-labs/honcho/tree/main/examples/langgraph) with examples in both [Python](https://github.com/plastic-labs/honcho/blob/main/examples/langgraph/python/main.py) and [TypeScript](https://github.com/plastic-labs/honcho/blob/main/examples/langgraph/typescript/main.ts)
|
|
</Note>
|
|
|
|
## What We're Building
|
|
|
|
We'll create a conversational agent that remembers and reasons over past exchanges with the user. Here's how the pieces fit together:
|
|
|
|
- **LangGraph** orchestrates the conversation flow
|
|
- **Honcho** stores messages and retrieves relevant context
|
|
- **Your LLM** generates responses using Honcho's formatted context
|
|
|
|
The key benefit: You don't manually manage conversation history, token limits, or message formatting. Honcho handles memory so you can focus on your agent's logic.
|
|
|
|
<Note>
|
|
This tutorial demonstrates a simple linear conversation flow to show
|
|
how Honcho integrates with LangGraph. For production applications,
|
|
you'll likely want to add LangGraph features like conditional routing,
|
|
tool calling, and multi-agent orchestration.
|
|
</Note>
|
|
|
|
## Setup
|
|
|
|
Install required packages:
|
|
|
|
<CodeGroup>
|
|
```bash Python (uv)
|
|
uv add honcho-ai langgraph langchain-core openai python-dotenv
|
|
```
|
|
|
|
```bash Python (pip)
|
|
pip install honcho-ai langgraph langchain-core openai python-dotenv
|
|
```
|
|
|
|
```bash TypeScript (npm)
|
|
npm install @honcho-ai/sdk @langchain/langgraph openai dotenv
|
|
```
|
|
|
|
```bash TypeScript (yarn)
|
|
yarn add @honcho-ai/sdk @langchain/langgraph openai dotenv
|
|
```
|
|
|
|
```bash TypeScript (pnpm)
|
|
pnpm add @honcho-ai/sdk @langchain/langgraph openai dotenv
|
|
```
|
|
</CodeGroup>
|
|
|
|
This tutorial uses OpenAI, but Honcho works with any LLM provider. Create a `.env` file with your API keys:
|
|
|
|
```bash
|
|
OPENAI_API_KEY=your_openai_key
|
|
```
|
|
|
|
<Note>
|
|
This tutorial uses the Honcho demo server at https://demo.honcho.dev which runs a small instance of Honcho on the latest version. For production, get your Honcho API key at [app.honcho.dev](https://app.honcho.dev). For local development, use `environment="local"`.
|
|
</Note>
|
|
|
|
## Initialize Clients
|
|
|
|
<CodeGroup>
|
|
```python Python
|
|
import os
|
|
from dotenv import load_dotenv
|
|
from typing_extensions import TypedDict
|
|
from honcho import Honcho, Peer, Session
|
|
from openai import OpenAI
|
|
from langgraph.graph import StateGraph, START, END
|
|
|
|
load_dotenv()
|
|
|
|
# Initialize Honcho
|
|
honcho = Honcho()
|
|
|
|
# Initialize OpenAI
|
|
llm = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
|
|
```
|
|
|
|
```typescript TypeScript
|
|
import * as dotenv from "dotenv";
|
|
import { Honcho, Peer, Session } from "@honcho-ai/sdk";
|
|
import OpenAI from "openai";
|
|
import { Annotation } from "@langchain/langgraph";
|
|
import { StateGraph, START, END } from "@langchain/langgraph";
|
|
import * as readline from "readline/promises";
|
|
|
|
dotenv.config();
|
|
|
|
// Initialize Honcho
|
|
const honcho = new Honcho({});
|
|
|
|
// Initialize OpenAI
|
|
const llm = new OpenAI({
|
|
apiKey: process.env.OPENAI_API_KEY
|
|
});
|
|
```
|
|
</CodeGroup>
|
|
|
|
## Define LangGraph State
|
|
|
|
Define your state schema to pass data through the graph. The state stores Honcho objects directly along with the current user message and assistant response.
|
|
|
|
<Note>
|
|
Before proceeding, it's important to understand Honcho's core concepts (`Peers` and `Sessions`). Review the [Honcho Architecture](/v2/documentation/core-concepts/architecture) to familiarize yourself with these primitives.
|
|
</Note>
|
|
|
|
<CodeGroup>
|
|
```python Python
|
|
class State(TypedDict):
|
|
user_message: str
|
|
assistant_response: str
|
|
user: Peer
|
|
assistant: Peer
|
|
session: Session
|
|
```
|
|
|
|
```typescript TypeScript
|
|
const StateAnnotation = Annotation.Root({
|
|
userMessage: Annotation<string>(),
|
|
assistantResponse: Annotation<string>(),
|
|
user: Annotation<Peer>(),
|
|
assistant: Annotation<Peer>(),
|
|
session: Annotation<Session>(),
|
|
});
|
|
|
|
type State = typeof StateAnnotation.State;
|
|
```
|
|
</CodeGroup>
|
|
|
|
## Build the LangGraph
|
|
|
|
Define your chatbot logic, using Honcho to retrieve conversation context. This function demonstrates how Honcho can store messages, retrieve context, and generate responses.
|
|
|
|
<CodeGroup>
|
|
```python Python
|
|
def chatbot(state: State):
|
|
user_message = state["user_message"]
|
|
|
|
# Get objects from state
|
|
user = state["user"]
|
|
assistant = state["assistant"]
|
|
session = state["session"]
|
|
|
|
# Step 1: Store the user's message in the session
|
|
# This adds it to Honcho's memory for future context retrieval
|
|
session.add_messages([user.message(user_message)])
|
|
|
|
# Step 2: Get context in OpenAI format with token limit
|
|
# get_context() retrieves relevant conversation history
|
|
# tokens=2000 limits the context to 2000 tokens to manage costs and fit within model limits
|
|
# to_openai() converts it to the format expected by OpenAI's API
|
|
messages = session.get_context(tokens=2000).to_openai(assistant=assistant)
|
|
|
|
# Step 3: Generate response using the context
|
|
response = llm.chat.completions.create(
|
|
model="gpt-5.1",
|
|
messages=messages
|
|
)
|
|
assistant_response = response.choices[0].message.content
|
|
|
|
# Step 4: Store assistant response in Honcho for future context
|
|
session.add_messages([assistant.message(assistant_response)])
|
|
|
|
return {"assistant_response": assistant_response}
|
|
```
|
|
|
|
```typescript TypeScript
|
|
async function chatbot(state: State) {
|
|
const userMessage = state.userMessage;
|
|
|
|
// Get objects from state
|
|
const user = state.user;
|
|
const assistant = state.assistant;
|
|
const session = state.session;
|
|
|
|
// Step 1: Store the user's message in the session
|
|
// This adds it to Honcho's memory for future context retrieval
|
|
await session.addMessages([user.message(userMessage)]);
|
|
|
|
// Step 2: Get context in OpenAI format with token limit
|
|
// getContext() retrieves relevant conversation history
|
|
// tokens: 2000 limits the context to 2000 tokens to manage costs and fit within model limits
|
|
// toOpenAI() converts it to the format expected by OpenAI's API
|
|
const messages = (await session.getContext({ tokens: 2000 })).toOpenAI(assistant);
|
|
|
|
// Step 3: Generate response using the context
|
|
const response = await llm.chat.completions.create({
|
|
model: "gpt-5.1",
|
|
messages: messages
|
|
});
|
|
const assistantResponse = response.choices[0].message.content!;
|
|
|
|
// Step 4: Store assistant response for future context
|
|
await session.addMessages([assistant.message(assistantResponse)]);
|
|
|
|
return { assistantResponse: assistantResponse };
|
|
}
|
|
```
|
|
</CodeGroup>
|
|
|
|
Now let's build the LangGraph:
|
|
<CodeGroup>
|
|
```python Python
|
|
graph = StateGraph(State) \
|
|
.add_node("chatbot", chatbot) \
|
|
.add_edge(START, "chatbot") \
|
|
.add_edge("chatbot", END) \
|
|
.compile()
|
|
```
|
|
|
|
```typescript TypeScript
|
|
const graph = new StateGraph(StateAnnotation)
|
|
.addNode("chatbot", chatbot)
|
|
.addEdge(START, "chatbot")
|
|
.addEdge("chatbot", END)
|
|
.compile();
|
|
```
|
|
</CodeGroup>
|
|
|
|
### Understanding get_context()
|
|
|
|
The [`get_context()`](/v2/documentation/core-concepts/features/get-context) method retrieves comprehensive conversation context and formats it for your LLM. It automatically:
|
|
|
|
- **Manages conversation history** - Tracks all messages and determines what's relevant
|
|
- **Respects token limits** - Stays within context window constraints without manual counting
|
|
- **Handles long conversations** - Combines recent detailed messages with summaries of older exchanges
|
|
- **Provides peer understanding** - Includes theory-of-mind representations and peer cards when requested
|
|
|
|
The `SessionContext` object always includes fields for messages, summaries, peer representations, and peer cards. By default, only `messages` and `summary` are populated. To populate peer-specific context, pass a `peer_target` parameter:
|
|
|
|
**Using `peer_target` for Context:**
|
|
|
|
- **Without `peer_perspective`**: Returns Honcho's omniscient view of `peer_target` (all observations and context)
|
|
- **With `peer_perspective`**: Returns what `peer_perspective` knows about `peer_target` (perspective-based observations and context)
|
|
|
|
That's it. Call `session.get_context().to_openai(assistant)` and you get properly formatted context tailored for your assistant.
|
|
|
|
<Tip>
|
|
**Adding System Prompts:** Since `get_context()` returns conversation messages, you can easily prepend custom system instructions. Just add your system prompt to the beginning of the messages array before sending it to your LLM: `[{"role": "system", "content": "..."}, ...context_messages]`.
|
|
</Tip>
|
|
|
|
<Note>
|
|
For more details on all available parameters, see [`get_context() documentation`](/v2/documentation/core-concepts/features/get-context)
|
|
</Note>
|
|
|
|
## Chat Loop
|
|
|
|
Now we'll create the main conversation function. To simplify logic, we initialize Honcho objects once per conversation and pass them through the LangGraph state.
|
|
|
|
The `run_conversation_turn` function initializes a Honcho `Session` and `Peer` objects, passes them to the LangGraph, and returns the assistant's response. By calling it repeatedly with the same `user_id` and in the same session, the chat builds context over time.
|
|
|
|
<Note>
|
|
**Production Usage:** Honcho accepts any nanoid-compatible string for `user_id` and `session_id`. You can use IDs directly from your authentication system (Auth0, Firebase, Clerk, etc.) and session management without modification.
|
|
|
|
This tutorial uses hardcoded values for simplicity.
|
|
</Note>
|
|
|
|
<CodeGroup>
|
|
```python Python
|
|
def run_conversation_turn(user_id: str, user_input: str, session_id: str | None = None):
|
|
if not session_id:
|
|
session_id = f"session_{user_id}"
|
|
|
|
# Initialize Honcho objects
|
|
user = honcho.peer(user_id)
|
|
assistant = honcho.peer("assistant")
|
|
session = honcho.session(session_id)
|
|
|
|
result = graph.invoke({
|
|
"user_message": user_input,
|
|
"user": user,
|
|
"assistant": assistant,
|
|
"session": session
|
|
})
|
|
|
|
return result["assistant_response"]
|
|
|
|
if __name__ == "__main__":
|
|
print("Welcome to the AI Assistant! How can I help you today?")
|
|
user_id = "test-user-123"
|
|
while True:
|
|
user_input = input("You: ")
|
|
if user_input.lower() in ['quit', 'exit']:
|
|
break
|
|
response = run_conversation_turn(user_id, user_input)
|
|
print(f"Assistant: {response}\n")
|
|
```
|
|
|
|
```typescript TypeScript
|
|
async function runConversationTurn(
|
|
userId: string,
|
|
userInput: string,
|
|
sessionId?: string
|
|
): Promise<string> {
|
|
if (!sessionId) {
|
|
sessionId = `session_${userId}`;
|
|
}
|
|
|
|
// Initialize Honcho objects
|
|
const user = await honcho.peer(userId);
|
|
const assistant = await honcho.peer("assistant");
|
|
const session = await honcho.session(sessionId);
|
|
|
|
const result = await graph.invoke({
|
|
userMessage: userInput,
|
|
user: user,
|
|
assistant: assistant,
|
|
session: session,
|
|
});
|
|
|
|
return result.assistantResponse;
|
|
}
|
|
|
|
// Interactive chat loop
|
|
async function main() {
|
|
console.log("Welcome to the AI Assistant! How can I help you today?");
|
|
const userId = "test-user-123";
|
|
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
while (true) {
|
|
const userInput = await rl.question("You: ");
|
|
if (userInput.toLowerCase() === "quit" || userInput.toLowerCase() === "exit") {
|
|
rl.close();
|
|
break;
|
|
}
|
|
const response = await runConversationTurn(userId, userInput);
|
|
console.log(`Assistant: ${response}\n`);
|
|
}
|
|
}
|
|
|
|
main();
|
|
```
|
|
</CodeGroup>
|
|
|
|
## Next Steps
|
|
|
|
Now that you have a working LangGraph integration with Honcho, you can:
|
|
|
|
- **Create custom [LangChain tools](https://docs.langchain.com/oss/python/langchain/tools#customize-tool-properties) for your agent** - to fully utilize Honcho's memory & context management features
|
|
- **Build a multi-agent LangGraph** where each agent is a Honcho `Peer` with its own memory
|
|
|
|
## Related Resources
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Get Context" icon="messages" href="/v2/documentation/core-concepts/features/get-context">
|
|
Learn more about retrieving and formatting conversation context
|
|
</Card>
|
|
<Card title="MCP Integration" icon="star-of-life" href="/v2/integrations/mcp">
|
|
Use Honcho in Claude Desktop with MCP
|
|
</Card>
|
|
</CardGroup>
|