ai-building-chatbots
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuild a Conversational AI Chatbot
构建对话式AI聊天机器人
Guide the user through building a multi-turn chatbot that remembers context, follows conversation flows, and produces high-quality responses. Uses DSPy for optimizable response generation and LangGraph for conversation state, memory, and flow control.
引导用户构建一款能够记住上下文、遵循对话流程并生成高质量响应的多轮对话聊天机器人。使用DSPy实现可优化的响应生成,使用LangGraph管理对话状态、记忆与流程控制。
Step 1: Define the conversation
步骤1:定义对话场景
Ask the user:
- What does the bot do? (answer questions, resolve issues, qualify leads, guide onboarding?)
- What states can the conversation be in? (greeting, gathering info, resolving, escalating, closing?)
- When should the bot escalate to a human? (complex issues, angry users, sensitive topics?)
- What docs or data should it draw from? (help articles, product docs, FAQs, database?)
询问用户:
- 机器人的用途是什么?(回答问题、解决问题、潜在客户资质审核、引导新用户入门?)
- 对话可能处于哪些状态?(问候、收集信息、解决问题、升级处理、结束对话?)
- 机器人何时需要将对话转交给人工?(复杂问题、用户情绪激动、敏感话题?)
- 机器人需要调用哪些文档或数据?(帮助文章、产品文档、FAQ、数据库?)
Step 2: Build the response module (DSPy)
步骤2:构建响应模块(DSPy)
The core of your chatbot is a DSPy module that generates responses given conversation history and context.
聊天机器人的核心是一个DSPy模块,可根据对话历史和上下文生成响应。
Basic response module
基础响应模块
python
import dspy
class ChatResponse(dspy.Signature):
"""Generate a helpful, on-brand response to the user's message."""
conversation_history: str = dspy.InputField(desc="Previous messages in the conversation")
context: str = dspy.InputField(desc="Relevant information from docs or database")
user_message: str = dspy.InputField(desc="The user's latest message")
response: str = dspy.OutputField(desc="Helpful response to the user")
class ChatBot(dspy.Module):
def __init__(self):
self.respond = dspy.ChainOfThought(ChatResponse)
def forward(self, conversation_history, context, user_message):
return self.respond(
conversation_history=conversation_history,
context=context,
user_message=user_message,
)python
import dspy
class ChatResponse(dspy.Signature):
"""Generate a helpful, on-brand response to the user's message."""
conversation_history: str = dspy.InputField(desc="Previous messages in the conversation")
context: str = dspy.InputField(desc="Relevant information from docs or database")
user_message: str = dspy.InputField(desc="The user's latest message")
response: str = dspy.OutputField(desc="Helpful response to the user")
class ChatBot(dspy.Module):
def __init__(self):
self.respond = dspy.ChainOfThought(ChatResponse)
def forward(self, conversation_history, context, user_message):
return self.respond(
conversation_history=conversation_history,
context=context,
user_message=user_message,
)With intent classification
带意图分类的模块
Route different intents to specialized handlers:
python
from typing import Literal
class ClassifyIntent(dspy.Signature):
"""Classify the user's intent from their message."""
conversation_history: str = dspy.InputField()
user_message: str = dspy.InputField()
intent: Literal["question", "complaint", "request", "greeting", "goodbye"] = dspy.OutputField()
class ChatBotWithRouting(dspy.Module):
def __init__(self):
self.classify = dspy.Predict(ClassifyIntent)
self.respond_question = dspy.ChainOfThought(AnswerQuestion)
self.respond_complaint = dspy.ChainOfThought(HandleComplaint)
self.respond_request = dspy.ChainOfThought(HandleRequest)
self.respond_greeting = dspy.Predict(Greeting)
def forward(self, conversation_history, context, user_message):
intent = self.classify(
conversation_history=conversation_history,
user_message=user_message,
).intent
handler = {
"question": self.respond_question,
"complaint": self.respond_complaint,
"request": self.respond_request,
"greeting": self.respond_greeting,
}.get(intent, self.respond_question)
return handler(
conversation_history=conversation_history,
context=context,
user_message=user_message,
)将不同意图路由至对应的专用处理器:
python
from typing import Literal
class ClassifyIntent(dspy.Signature):
"""Classify the user's intent from their message."""
conversation_history: str = dspy.InputField()
user_message: str = dspy.InputField()
intent: Literal["question", "complaint", "request", "greeting", "goodbye"] = dspy.OutputField()
class ChatBotWithRouting(dspy.Module):
def __init__(self):
self.classify = dspy.Predict(ClassifyIntent)
self.respond_question = dspy.ChainOfThought(AnswerQuestion)
self.respond_complaint = dspy.ChainOfThought(HandleComplaint)
self.respond_request = dspy.ChainOfThought(HandleRequest)
self.respond_greeting = dspy.Predict(Greeting)
def forward(self, conversation_history, context, user_message):
intent = self.classify(
conversation_history=conversation_history,
user_message=user_message,
).intent
handler = {
"question": self.respond_question,
"complaint": self.respond_complaint,
"request": self.respond_request,
"greeting": self.respond_greeting,
}.get(intent, self.respond_question)
return handler(
conversation_history=conversation_history,
context=context,
user_message=user_message,
)Step 3: Add conversation state (LangGraph)
步骤3:添加对话状态管理(LangGraph)
LangGraph manages the conversation flow — what state the bot is in, when to transition, and when to escalate.
LangGraph负责管理对话流程——包括机器人当前所处的状态、状态切换时机以及何时需要升级处理。
Define conversation state
定义对话状态
python
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
class ConversationState(TypedDict):
messages: Annotated[list[dict], operator.add] # full message history
current_intent: str
context: str # retrieved docs/data for current turn
escalate: bool # whether to hand off to a human
resolved: bool # whether the issue is resolved
turn_count: intpython
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator
class ConversationState(TypedDict):
messages: Annotated[list[dict], operator.add] # full message history
current_intent: str
context: str # retrieved docs/data for current turn
escalate: bool # whether to hand off to a human
resolved: bool # whether the issue is resolved
turn_count: intBuild the conversation graph
构建对话图
python
import dspypython
import dspyInitialize DSPy modules
Initialize DSPy modules
classifier = dspy.Predict(ClassifyIntent)
responder = dspy.ChainOfThought(ChatResponse)
def classify_node(state: ConversationState) -> dict:
"""Classify the user's intent."""
history = format_history(state["messages"][:-1])
user_msg = state["messages"][-1]["content"]
result = classifier(conversation_history=history, user_message=user_msg)
return {"current_intent": result.intent}
def retrieve_node(state: ConversationState) -> dict:
"""Retrieve relevant docs for the current message."""
user_msg = state["messages"][-1]["content"]
# Your retrieval logic here (see /ai-searching-docs)
docs = retrieve_relevant_docs(user_msg)
return {"context": "\n".join(docs)}
def respond_node(state: ConversationState) -> dict:
"""Generate a response using DSPy."""
history = format_history(state["messages"][:-1])
user_msg = state["messages"][-1]["content"]
result = responder(
conversation_history=history,
context=state["context"],
user_message=user_msg,
)
return {
"messages": [{"role": "assistant", "content": result.response}],
"turn_count": state["turn_count"] + 1,
}
def check_escalation(state: ConversationState) -> dict:
"""Decide if this needs human handoff."""
should_escalate = (
state["current_intent"] == "complaint"
and state["turn_count"] > 3
)
return {"escalate": should_escalate}
def format_history(messages: list[dict]) -> str:
return "\n".join(f"{m['role']}: {m['content']}" for m in messages[-10:])
classifier = dspy.Predict(ClassifyIntent)
responder = dspy.ChainOfThought(ChatResponse)
def classify_node(state: ConversationState) -> dict:
"""Classify the user's intent."""
history = format_history(state["messages"][:-1])
user_msg = state["messages"][-1]["content"]
result = classifier(conversation_history=history, user_message=user_msg)
return {"current_intent": result.intent}
def retrieve_node(state: ConversationState) -> dict:
"""Retrieve relevant docs for the current message."""
user_msg = state["messages"][-1]["content"]
# Your retrieval logic here (see /ai-searching-docs)
docs = retrieve_relevant_docs(user_msg)
return {"context": "\n".join(docs)}
def respond_node(state: ConversationState) -> dict:
"""Generate a response using DSPy."""
history = format_history(state["messages"][:-1])
user_msg = state["messages"][-1]["content"]
result = responder(
conversation_history=history,
context=state["context"],
user_message=user_msg,
)
return {
"messages": [{"role": "assistant", "content": result.response}],
"turn_count": state["turn_count"] + 1,
}
def check_escalation(state: ConversationState) -> dict:
"""Decide if this needs human handoff."""
should_escalate = (
state["current_intent"] == "complaint"
and state["turn_count"] > 3
)
return {"escalate": should_escalate}
def format_history(messages: list[dict]) -> str:
return "\n".join(f"{m['role']}: {m['content']}" for m in messages[-10:])
Build the graph
Build the graph
graph = StateGraph(ConversationState)
graph.add_node("classify", classify_node)
graph.add_node("retrieve", retrieve_node)
graph.add_node("respond", respond_node)
graph.add_node("check_escalation", check_escalation)
graph.add_edge(START, "classify")
graph.add_edge("classify", "retrieve")
graph.add_edge("retrieve", "respond")
graph.add_edge("respond", "check_escalation")
def route_after_escalation_check(state: ConversationState) -> str:
if state["escalate"]:
return "escalate"
return "done"
graph.add_conditional_edges(
"check_escalation",
route_after_escalation_check,
{"escalate": END, "done": END},
)
app = graph.compile()
undefinedgraph = StateGraph(ConversationState)
graph.add_node("classify", classify_node)
graph.add_node("retrieve", retrieve_node)
graph.add_node("respond", respond_node)
graph.add_node("check_escalation", check_escalation)
graph.add_edge(START, "classify")
graph.add_edge("classify", "retrieve")
graph.add_edge("retrieve", "respond")
graph.add_edge("respond", "check_escalation")
def route_after_escalation_check(state: ConversationState) -> str:
if state["escalate"]:
return "escalate"
return "done"
graph.add_conditional_edges(
"check_escalation",
route_after_escalation_check,
{"escalate": END, "done": END},
)
app = graph.compile()
undefinedRun a conversation turn
运行单轮对话
python
result = app.invoke({
"messages": [{"role": "user", "content": "How do I reset my password?"}],
"current_intent": "",
"context": "",
"escalate": False,
"resolved": False,
"turn_count": 0,
})
print(result["messages"][-1]["content"])python
result = app.invoke({
"messages": [{"role": "user", "content": "How do I reset my password?"}],
"current_intent": "",
"context": "",
"escalate": False,
"resolved": False,
"turn_count": 0,
})
print(result["messages"][-1]["content"])Step 4: Add memory
步骤4:添加记忆功能
Session memory with checkpointing
带检查点的会话记忆
LangGraph's checkpointer persists conversation state across requests:
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)LangGraph的检查点功能可在多次请求间持久化对话状态:
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)Each user session gets a unique thread_id
Each user session gets a unique thread_id
config = {"configurable": {"thread_id": "user-abc-123"}}
config = {"configurable": {"thread_id": "user-abc-123"}}
Turn 1
Turn 1
result = app.invoke(
{"messages": [{"role": "user", "content": "Hi, I need help with billing"}],
"current_intent": "", "context": "", "escalate": False, "resolved": False, "turn_count": 0},
config=config,
)
result = app.invoke(
{"messages": [{"role": "user", "content": "Hi, I need help with billing"}],
"current_intent": "", "context": "", "escalate": False, "resolved": False, "turn_count": 0},
config=config,
)
Turn 2 — state is preserved, the bot remembers the conversation
Turn 2 — state is preserved, the bot remembers the conversation
result = app.invoke(
{"messages": [{"role": "user", "content": "I was charged twice last month"}]},
config=config,
)
For production, use a persistent backend:
```python
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver(conn_string="postgresql://user:pass@localhost/chatbot")
app = graph.compile(checkpointer=checkpointer)result = app.invoke(
{"messages": [{"role": "user", "content": "I was charged twice last month"}]},
config=config,
)
生产环境中,可使用持久化后端:
```python
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver(conn_string="postgresql://user:pass@localhost/chatbot")
app = graph.compile(checkpointer=checkpointer)Conversation summary for long chats
长对话总结
When conversations get long, summarize older messages to stay within token limits:
python
class SummarizeConversation(dspy.Signature):
"""Summarize the conversation so far, preserving key details."""
conversation: str = dspy.InputField()
summary: str = dspy.OutputField(desc="Concise summary of the conversation so far")
summarizer = dspy.Predict(SummarizeConversation)
def maybe_summarize(state: ConversationState) -> dict:
"""Summarize if conversation is getting long."""
if len(state["messages"]) > 20:
history = format_history(state["messages"][:-5])
summary = summarizer(conversation=history).summary
# Keep summary + last 5 messages
return {
"messages": [
{"role": "system", "content": f"Summary of earlier conversation: {summary}"},
*state["messages"][-5:],
]
}
return {}当对话过长时,总结早期消息以控制token用量:
python
class SummarizeConversation(dspy.Signature):
"""Summarize the conversation so far, preserving key details."""
conversation: str = dspy.InputField()
summary: str = dspy.OutputField(desc="Concise summary of the conversation so far")
summarizer = dspy.Predict(SummarizeConversation)
def maybe_summarize(state: ConversationState) -> dict:
"""Summarize if conversation is getting long."""
if len(state["messages"]) > 20:
history = format_history(state["messages"][:-5])
summary = summarizer(conversation=history).summary
# Keep summary + last 5 messages
return {
"messages": [
{"role": "system", "content": f"Summary of earlier conversation: {summary}"},
*state["messages"][-5:],
]
}
return {}Step 5: Ground responses in docs
步骤5:基于文档生成响应
Retrieve relevant documents each turn to keep responses factual.
python
class DocGroundedResponse(dspy.Signature):
"""Answer the user's question based on the provided documentation.
Only use information from the docs. If the docs don't cover it, say so."""
conversation_history: str = dspy.InputField()
docs: list[str] = dspy.InputField(desc="Relevant documentation passages")
user_message: str = dspy.InputField()
response: str = dspy.OutputField()
class GroundedChatBot(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.respond = dspy.ChainOfThought(DocGroundedResponse)
def forward(self, conversation_history, user_message):
# Retrieve docs relevant to the current message
docs = self.retriever(user_message).passages
return self.respond(
conversation_history=conversation_history,
docs=docs,
user_message=user_message,
)See for setting up retrievers and vector stores, including loading data from PDFs, Notion, and other sources with LangChain document loaders.
/ai-searching-docs每轮对话都检索相关文档,确保响应真实可信。
python
class DocGroundedResponse(dspy.Signature):
"""Answer the user's question based on the provided documentation.
Only use information from the docs. If the docs don't cover it, say so."""
conversation_history: str = dspy.InputField()
docs: list[str] = dspy.InputField(desc="Relevant documentation passages")
user_message: str = dspy.InputField()
response: str = dspy.OutputField()
class GroundedChatBot(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.respond = dspy.ChainOfThought(DocGroundedResponse)
def forward(self, conversation_history, user_message):
# Retrieve docs relevant to the current message
docs = self.retriever(user_message).passages
return self.respond(
conversation_history=conversation_history,
docs=docs,
user_message=user_message,
)如需设置检索器和向量存储,包括使用LangChain文档加载器从PDF、Notion等来源加载数据,请参考 。
/ai-searching-docsStep 6: Add guardrails
步骤6:添加安全防护
Response quality with DSPy assertions
基于DSPy断言的响应质量管控
python
class GuardedChatBot(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.respond = dspy.ChainOfThought(DocGroundedResponse)
def forward(self, conversation_history, user_message):
docs = self.retriever(user_message).passages
result = self.respond(
conversation_history=conversation_history,
docs=docs,
user_message=user_message,
)
# Guardrails
dspy.Suggest(
len(result.response.split()) < 200,
"Keep responses concise — under 200 words",
)
dspy.Suggest(
not any(word in result.response.lower() for word in ["obviously", "clearly", "simply"]),
"Avoid condescending language",
)
dspy.Assert(
"I am an AI" not in result.response,
"Don't break character with meta-statements",
)
return resultpython
class GuardedChatBot(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.respond = dspy.ChainOfThought(DocGroundedResponse)
def forward(self, conversation_history, user_message):
docs = self.retriever(user_message).passages
result = self.respond(
conversation_history=conversation_history,
docs=docs,
user_message=user_message,
)
# Guardrails
dspy.Suggest(
len(result.response.split()) < 200,
"Keep responses concise — under 200 words",
)
dspy.Suggest(
not any(word in result.response.lower() for word in ["obviously", "clearly", "simply"]),
"Avoid condescending language",
)
dspy.Assert(
"I am an AI" not in result.response,
"Don't break character with meta-statements",
)
return resultHuman-in-the-loop for sensitive actions
敏感操作的人工介入机制
Use LangGraph's interrupt to pause before the bot takes real actions:
python
app = graph.compile(
checkpointer=checkpointer,
interrupt_before=["execute_refund", "cancel_account"], # pause here
)使用LangGraph的中断功能,在机器人执行实际操作前暂停:
python
app = graph.compile(
checkpointer=checkpointer,
interrupt_before=["execute_refund", "cancel_account"], # pause here
)Bot runs until it reaches a sensitive action
Bot runs until it reaches a sensitive action
result = app.invoke(input_state, config)
result = app.invoke(input_state, config)
Human agent reviews the proposed action
Human agent reviews the proposed action
If approved, resume:
If approved, resume:
result = app.invoke(None, config) # continues from checkpoint
undefinedresult = app.invoke(None, config) # continues from checkpoint
undefinedStep 7: Optimize and evaluate
步骤7:优化与评估
Conversation-level metrics
对话级指标
python
def chatbot_metric(example, prediction, trace=None):
"""Score a single conversation turn."""
judge = dspy.Predict(JudgeTurn)
result = judge(
user_message=example.user_message,
expected_response=example.response,
actual_response=prediction.response,
conversation_history=example.conversation_history,
)
return result.is_good
class JudgeTurn(dspy.Signature):
"""Judge if the chatbot response is helpful, accurate, and on-topic."""
user_message: str = dspy.InputField()
expected_response: str = dspy.InputField()
actual_response: str = dspy.InputField()
conversation_history: str = dspy.InputField()
is_good: bool = dspy.OutputField()python
def chatbot_metric(example, prediction, trace=None):
"""Score a single conversation turn."""
judge = dspy.Predict(JudgeTurn)
result = judge(
user_message=example.user_message,
expected_response=example.response,
actual_response=prediction.response,
conversation_history=example.conversation_history,
)
return result.is_good
class JudgeTurn(dspy.Signature):
"""Judge if the chatbot response is helpful, accurate, and on-topic."""
user_message: str = dspy.InputField()
expected_response: str = dspy.InputField()
actual_response: str = dspy.InputField()
conversation_history: str = dspy.InputField()
is_good: bool = dspy.OutputField()Build a training set from real conversations
基于真实对话构建训练集
python
trainset = []
for convo in real_conversations:
for turn in convo["turns"]:
trainset.append(
dspy.Example(
conversation_history=turn["history"],
user_message=turn["user_message"],
context=turn["context"],
response=turn["response"],
).with_inputs("conversation_history", "user_message", "context")
)python
trainset = []
for convo in real_conversations:
for turn in convo["turns"]:
trainset.append(
dspy.Example(
conversation_history=turn["history"],
user_message=turn["user_message"],
context=turn["context"],
response=turn["response"],
).with_inputs("conversation_history", "user_message", "context")
)Optimize
优化模型
python
optimizer = dspy.MIPROv2(metric=chatbot_metric, auto="medium")
optimized_bot = optimizer.compile(chatbot, trainset=trainset)python
optimizer = dspy.MIPROv2(metric=chatbot_metric, auto="medium")
optimized_bot = optimizer.compile(chatbot, trainset=trainset)Save optimized prompts
Save optimized prompts
optimized_bot.save("chatbot_optimized.json")
undefinedoptimized_bot.save("chatbot_optimized.json")
undefinedKey patterns
核心模式
- DSPy for response generation, LangGraph for flow control — DSPy modules handle what the bot says; LangGraph handles conversation state and routing
- Checkpointing is your memory — use LangGraph's checkpointer so conversations persist across HTTP requests
- Retrieve every turn — don't assume context from earlier turns is still relevant; re-retrieve each time
- Summarize long conversations — once past ~20 messages, summarize older context to stay within token limits
- Classify intent early — knowing the user's intent lets you route to specialized handlers
- Interrupt before real actions — use LangGraph's so humans approve refunds, cancellations, etc.
interrupt_before - Optimize on real conversations — collect actual chat logs to build training data for DSPy optimization
- DSPy负责响应生成,LangGraph负责流程控制 —— DSPy模块处理机器人的回复内容;LangGraph处理对话状态与路由
- 检查点即记忆 —— 使用LangGraph的检查点功能,让对话在HTTP请求间保持持续
- 每轮都检索 —— 不要假设早期对话的上下文仍然相关,每轮对话都重新检索信息
- 长对话总结 —— 当对话超过约20条消息时,总结早期上下文以控制token用量
- 提前分类意图 —— 了解用户意图后,可将对话路由至专用处理器
- 敏感操作前中断 —— 使用LangGraph的功能,让人工审核退款、账户注销等操作
interrupt_before - 基于真实对话优化 —— 收集实际聊天记录,为DSPy优化构建训练数据
Additional resources
额外资源
- For worked examples (support bot, FAQ assistant), see examples.md
- For the LangChain/LangGraph API reference, see
docs/langchain-langgraph-reference.md - Need to search docs for grounding? Use
/ai-searching-docs - Need the bot to take actions (call APIs, tools)? Use
/ai-taking-actions - Building multiple bots that work together? Use
/ai-coordinating-agents - Next: to measure and improve your chatbot
/ai-improving-accuracy
- 如需完整示例(支持机器人、FAQ助手),请查看 examples.md
- 如需LangChain/LangGraph API参考文档,请查看
docs/langchain-langgraph-reference.md - 如需基于文档的搜索功能,请使用
/ai-searching-docs - 如需让机器人执行操作(调用API、工具),请使用
/ai-taking-actions - 如需构建协同工作的多机器人系统,请使用
/ai-coordinating-agents - 下一步:使用 测量并提升聊天机器人的准确度
/ai-improving-accuracy