在构建AI应用时,我们常常听到"上下文"这个术语,但LangGraph通过"状态"这一概念,将上下文管理提升到了新的高度。本文将通过一个实战例子,深入探讨状态即上下文的核心理念,并详解四种上下文管理方法。

引言:为什么需要上下文管理?

想象一下这个场景:你正在和旅行助手聊天,已经讨论了目的地、预算和兴趣。当你说"帮我订一个那里附近的酒店"时,AI需要理解"那里"指的是之前提到的目的地,"附近"需要结合之前的兴趣点。这就是上下文的力量

传统AI应用往往在上下文管理上力不从心,而LangGraph通过状态(State) 这一核心概念,为我们提供了一套完整的解决方案。

实战例子:智能旅行规划助手

让我们从一个完整的例子开始,逐步深入理解状态即上下文的概念:

python

import os
from typing import TypedDict, List, Annotated, Optional, Literal
from langgraph.graph import StateGraph, END, START
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from datetime import datetime

# 定义状态结构 - 这就是我们的上下文容器
class TravelState(TypedDict):
    """旅行规划的状态/上下文"""
    messages: Annotated[List, add_messages]  # 对话历史上下文
    user_id: str                             # 用户身份上下文
    destination: Optional[str]               # 目的地上下文
    budget: Optional[float]                  # 预算上下文
    interests: List[str]                     # 兴趣偏好上下文
    travel_dates: Optional[str]              # 旅行日期上下文
    current_step: str                        # 当前流程步骤上下文
    extracted_info: dict                     # 已提取信息上下文
    conversation_summary: str                # 对话摘要上下文

状态即上下文:LangGraph的核心哲学

在LangGraph中,状态不仅仅是数据容器,更是工作流的智能记忆系统。让我们看看这个旅行助手如何使用状态管理上下文:

python

# 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo")

def extract_travel_info(state: TravelState):
    """提取旅行信息节点 - 展示上下文写入"""
    # 获取完整的对话历史作为上下文
    conversation = state["messages"]
    
    # 使用LLM从对话中提取结构化信息
    extraction_prompt = f"""
    从以下对话中提取旅行信息:
    {conversation}
    
    请提取以下信息(如果提到):
    1. 目的地
    2. 预算(数字)
    3. 兴趣(列表)
    4. 旅行日期
    
    以JSON格式返回。
    """
    
    response = llm.invoke([{"role": "user", "content": extraction_prompt}])
    
    # 解析响应并更新状态(写入上下文)
    import json
    try:
        extracted = json.loads(response.content)
    except:
        extracted = {}
    
    # 更新状态 - 这就是上下文写入
    updates = {
        "destination": extracted.get("destination"),
        "budget": extracted.get("budget"),
        "interests": extracted.get("interests", []),
        "travel_dates": extracted.get("travel_dates"),
        "extracted_info": extracted,
        "current_step": "planning"
    }
    
    # 同时将提取结果添加到对话历史
    updates["messages"] = [{
        "role": "assistant", 
        "content": f"已提取您的旅行偏好:{extracted}"
    }]
    
    return updates

def generate_recommendations(state: TravelState):
    """生成推荐节点 - 展示上下文使用"""
    # 基于完整上下文生成个性化推荐
    prompt = f"""
    基于以下旅行上下文为用户生成推荐:
    
    用户信息:
    - 目的地:{state.get('destination', '未指定')}
    - 预算:{state.get('budget', '未指定')}
    - 兴趣:{', '.join(state.get('interests', []))}
    - 旅行日期:{state.get('travel_dates', '未指定')}
    
    对话历史摘要:{state.get('conversation_summary', '无')}
    
    请提供:
    1. 3个景点推荐
    2. 餐饮建议
    3. 活动推荐
    """
    
    response = llm.invoke([{"role": "user", "content": prompt}])
    
    return {
        "messages": [{
            "role": "assistant", 
            "content": f"基于您的偏好,我的推荐:\n{response.content}"
        }],
        "current_step": "completed"
    }

四大上下文管理方法

方法一:写入 - 构建完整上下文

写入是上下文管理的基础,确保所有相关信息都被记录到状态中:

python

def collect_destination(state: TravelState):
    """收集目的地信息 - 上下文写入示例"""
    # 从对话历史中获取最新的用户消息
    last_user_message = None
    for msg in reversed(state["messages"]):
        if msg["role"] == "user":
            last_user_message = msg["content"]
            break
    
    if last_user_message and "巴黎" in last_user_message:
        # 写入目的地上下文
        # 同时写入相关上下文(如假设的预算和兴趣)
        return {
            "destination": "巴黎",
            "budget": 5000.0,  # 假设的默认预算
            "interests": ["艺术", "美食"],  # 假设的兴趣
            "current_step": "collecting_details",
            "messages": [{
                "role": "assistant", 
                "content": "巴黎是个好选择!您是喜欢艺术还是美食更多呢?"
            }]
        }
    
    return {"messages": [{"role": "assistant", "content": "您想去哪里旅行呢?"}]}

关键点:写入不仅仅是添加数据,更是建立数据间的关联,形成丰富的上下文网络。

方法二:压缩 - 管理上下文长度

当对话历史过长时,我们需要压缩上下文以节省token并保持相关性:

python

def compress_conversation(state: TravelState, max_messages: int = 10):
    """压缩对话历史 - 上下文压缩示例"""
    messages = state["messages"]
    
    if len(messages) <= max_messages:
        return state  # 无需压缩
    
    # 策略1:保留系统消息和最近对话
    system_messages = [m for m in messages if m["role"] == "system"]
    recent_messages = messages[-max_messages//2:]  # 保留最近的一半
    
    # 策略2:总结旧的历史
    old_messages = messages[:len(messages)-max_messages//2]
    summary_prompt = f"请用一段话总结以下对话的核心内容:\n{old_messages}"
    
    summary_response = llm.invoke([{"role": "user", "content": summary_prompt}])
    summary_message = {
        "role": "system", 
        "content": f"对话历史摘要:{summary_response.content}"
    }
    
    # 构建压缩后的消息列表
    compressed_messages = system_messages + [summary_message] + recent_messages
    
    # 更新状态
    return {
        "messages": compressed_messages,
        "conversation_summary": summary_response.content
    }

# 在图中添加压缩节点
def compression_node(state: TravelState):
    """压缩节点 - 自动触发压缩"""
    if len(state["messages"]) > 15:  # 超过15条消息时压缩
        return compress_conversation(state)
    return {}  # 无更新

方法三:选择 - 智能上下文筛选

不是所有上下文对每个任务都重要,选择让我们能提取最相关的部分:

python

def select_relevant_context(state: TravelState, context_type: str):
    """根据任务类型选择相关上下文"""
    
    if context_type == "budget_planning":
        # 对于预算规划,选择与预算相关的上下文
        relevant_messages = []
        for msg in state["messages"]:
            content = msg.get("content", "").lower()
            if any(word in content for word in ["预算", "价格", "花费", "成本", "money", "budget"]):
                relevant_messages.append(msg)
        
        # 添加上下文摘要
        context_summary = f"""
        预算规划相关上下文:
        目的地:{state.get('destination')}
        当前预算:{state.get('budget')}
        相关对话历史:{len(relevant_messages)}条相关消息
        """
        
        return {
            "selected_context": relevant_messages,
            "context_summary": context_summary
        }
    
    elif context_type == "activity_suggestion":
        # 对于活动建议,选择兴趣相关的上下文
        interests = state.get("interests", [])
        relevant_messages = []
        
        for msg in state["messages"]:
            content = msg.get("content", "")
            if any(interest in content for interest in interests):
                relevant_messages.append(msg)
        
        return {
            "selected_context": relevant_messages,
            "context_summary": f"用户兴趣:{', '.join(interests)}"
        }
    
    return {"selected_context": state["messages"][-5:]}  # 默认选择最近5条

# 应用选择策略的节点
def budget_advisor_node(state: TravelState):
    """预算建议节点 - 使用选择的上下文"""
    # 选择与预算相关的上下文
    selected = select_relevant_context(state, "budget_planning")
    
    prompt = f"""
    基于以下上下文提供预算建议:
    
    {selected['context_summary']}
    
    相关对话片段:
    {selected['selected_context'][-3:] if selected['selected_context'] else '无'}
    
    请提供详细的预算分配建议。
    """
    
    response = llm.invoke([{"role": "user", "content": prompt}])
    
    return {
        "messages": [{"role": "assistant", "content": response.content}]
    }

方法四:隔离 - 上下文边界管理

有些任务需要独立的上下文空间,避免信息污染:

python

def create_subgraph_for_flight_search(state: TravelState):
    """创建机票搜索子图 - 上下文隔离示例"""
    # 提取机票搜索所需的最小上下文
    flight_context = {
        "destination": state.get("destination"),
        "travel_dates": state.get("travel_dates"),
        "user_id": state.get("user_id")
    }
    
    # 在隔离的上下文中执行机票搜索
    # 这里不会接触到主对话的其他细节
    def flight_search_subgraph(flight_state):
        """机票搜索子图 - 独立上下文"""
        # 模拟API调用
        flights = [
            {"airline": "Airline A", "price": 800, "time": "10:00"},
            {"airline": "Airline B", "price": 750, "time": "14:00"}
        ]
        
        # 根据预算过滤(仅使用子图自己的上下文)
        budget = state.get("budget", float('inf'))
        filtered_flights = [f for f in flights if f["price"] <= budget/3]  # 假设机票占预算1/3
        
        return {
            "available_flights": filtered_flights,
            "search_completed": True
        }
    
    # 执行子图
    flight_results = flight_search_subgraph(flight_context)
    
    # 将结果整合回主上下文
    return {
        "extracted_info": {
            **state.get("extracted_info", {}),
            "flight_options": flight_results["available_flights"]
        },
        "messages": [{
            "role": "assistant", 
            "content": f"找到{len(flight_results['available_flights'])}个航班选项"
        }]
    }

完整的工作流示例

让我们将这些方法整合到一个完整的旅行规划图中:

python

from langgraph.graph import StateGraph, END

def build_travel_planner():
    """构建完整的旅行规划图"""
    builder = StateGraph(TravelState)
    
    # 添加节点
    builder.add_node("start_conversation", collect_destination)
    builder.add_node("extract_info", extract_travel_info)
    builder.add_node("compress_context", compression_node)
    builder.add_node("budget_advice", budget_advisor_node)
    builder.add_node("flight_search", create_subgraph_for_flight_search)
    builder.add_node("make_recommendations", generate_recommendations)
    
    # 设置流程
    builder.set_entry_point("start_conversation")
    
    # 定义边
    builder.add_edge("start_conversation", "extract_info")
    builder.add_conditional_edges(
        "extract_info",
        lambda state: "compress" if len(state.get("messages", [])) > 15 else "budget_advice"
    )
    builder.add_edge("compress_context", "budget_advice")
    builder.add_edge("budget_advice", "flight_search")
    builder.add_edge("flight_search", "make_recommendations")
    builder.add_edge("make_recommendations", END)
    
    return builder.compile()

# 使用示例
async def main():
    travel_planner = build_travel_planner()
    
    # 初始状态
    initial_state = {
        "messages": [
            {"role": "system", "content": "你是一个专业的旅行规划助手"},
            {"role": "user", "content": "我想去巴黎旅行,预算5000元"}
        ],
        "user_id": "user123",
        "destination": None,
        "budget": None,
        "interests": [],
        "travel_dates": None,
        "current_step": "start",
        "extracted_info": {},
        "conversation_summary": ""
    }
    
    # 执行图
    result = await travel_planner.ainvoke(initial_state)
    
    print("最终推荐:", result["messages"][-1]["content"])
    print("提取的信息:", result["extracted_info"])
    print("当前步骤:", result["current_step"])

四种方法的对比总结

方法 目的 适用场景 示例
写入 记录和丰富上下文 信息收集阶段 记录用户偏好、提取结构化数据
压缩 减少上下文长度 长对话、节省token成本 总结旧历史、保留关键信息
选择 提取相关上下文 任务专业化处理 预算规划只关注财务相关对话
隔离 创建独立上下文 子任务、避免污染 机票搜索不接触餐饮偏好

最佳实践建议

  1. 分层管理上下文:将上下文分为会话级、用户级、应用级

  2. 适时压缩:定期检查上下文长度,避免无限增长

  3. 智能选择:根据任务类型动态选择相关上下文

  4. 明确隔离边界:清晰定义子任务的输入输出,避免隐式依赖

结语

LangGraph通过状态即上下文的理念,为AI应用提供了强大的上下文管理能力。掌握写入、压缩、选择和隔离这四种方法,你将能够构建出真正智能、高效且可维护的对话系统。

上下文不再是隐式的、难以管理的负担,而是成为了我们构建智能应用的强大工具。在LangGraph的世界里,状态不仅记录了发生了什么,更指导着接下来会发生什么——这就是现代AI应用上下文管理的艺术。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐