✨ 复制成功!

以文会友,打造好学人设!

📋 已复制到剪贴板:

《Agent Harness入门教程》


1 AI Agent与Harness概述
    1.1  什么是AI Agent

    1.2  什么是Agent Harness

    1.3  为什么需要Harness

2 核心概念与架构模式
    2.1  Agent的核心循环

    2.2  Harness的典型架构

    2.3  单Agent vs 多Agent

    2.4  Agent生命周期

    2.5  核心术语表

3 环境搭建与基础组件
    3.1  选择LLM Provider

    3.2  搭建最小Harness

    3.3  实现工具调用机制

    3.4  结构化输出与解析

    3.5  日志与调试基础

4 工具系统设计
    4.1  工具的定义与注册

    4.2  工具Schema设计

    4.3  安全沙箱

5 记忆与会话管理
6 Agent推理与规划
7 商业项目实战练习

会话状态管理:多轮对话的连续性

创建时间:2026-09-06 更新时间:2026-09-06 阅读次数:1100 次

一个容易被忽视的问题

看下面这段对话:

用户:帮我查一下明天的天气
Agent:明天北京晴,26°C。

用户:那后天呢?
Agent:抱歉,我不确定您想查询哪个城市的天气。请告诉我城市名称。

Agent的回答很糟糕。用户显然是在延续上一个话题——查询北京的天气,只是日期从"明天"变成了"后天"。但Agent"忘记"了对话的上下文,把"那后天呢"当作一个独立的新问题来处理。

这暴露了一个关键问题:多轮对话中的信息是渐进的、依赖的、省略的。 用户不会在每一轮都重复完整的信息——他们假设Agent记得之前说过什么。当Agent"忘记"时,对话的流畅性就被打破了。

会话状态管理的目标就是:让Agent在跨多轮对话时保持对上下文的"意识"——知道话题是什么、已经确认了什么、还需要补充什么。

会话状态的多层结构

一个完整的会话状态包含三个层次:

层次包含字段
会话层(Session Level)· 会话ID
· 开始时间
· 用户身份
· 当前话题
任务层(Task Level)· 当前任务目标
· 已完成的子任务
· 待确认的信息
· 任务的中间结果
消息层(Message Level)· 对话历史(短期记忆)
· 每条消息的角色和内容

消息层在5.1节中已经处理过了(短期记忆),会话层和任务层是本节的重点。它们是消息之上的结构化抽象——不直接存储消息内容,而是跟踪对话的"元信息"。

实现会话状态管理

第一步:定义会话和任务状态

from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from datetime import datetime
from enum import Enum
import uuid

class TaskStatus(Enum):
    """任务状态"""
    NOT_STARTED = "not_started"
    IN_PROGRESS = "in_progress"
    WAITING_CONFIRMATION = "waiting_confirmation"
    WAITING_INPUT = "waiting_input"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"

class ConversationTopic(Enum):
    """对话话题类型"""
    GENERAL_QUERY = "general_query"
    TASK_EXECUTION = "task_execution"
    CLARIFICATION = "clarification"
    FOLLOW_UP = "follow_up"
    ERROR_RECOVERY = "error_recovery"

@dataclass
class TaskState:
    """一个任务的执行状态"""
    task_id: str
    description: str
    status: TaskStatus
    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
    subtasks_completed: List[str] = field(default_factory=list)
    pending_confirmations: List[str] = field(default_factory=list)
    pending_inputs: List[str] = field(default_factory=list)
    result: Optional[str] = None
    error: Optional[str] = None

@dataclass
class SessionState:
    """一次会话的完整状态"""
    session_id: str
    user_id: Optional[str] = None
    started_at: str = field(default_factory=lambda: datetime.now().isoformat())
    last_activity_at: str = field(default_factory=lambda: datetime.now().isoformat())
    current_topic: ConversationTopic = ConversationTopic.GENERAL_QUERY
    current_task: Optional[TaskState] = None
    task_history: List[TaskState] = field(default_factory=list)
    confirmed_facts: Dict[str, Any] = field(default_factory=dict)
    unresolved_questions: List[str] = field(default_factory=list)
    conversation_turns: int = 0
    metadata: Dict[str, Any] = field(default_factory=dict)

    def touch(self):
        """更新最后活动时间"""
        self.last_activity_at = datetime.now().isoformat()
        self.conversation_turns += 1

第二步:实现状态管理器

class SessionStateManager:
    """会话状态管理器"""

    def __init__(self, session_timeout_minutes: int = 30):
        self.sessions: Dict[str, SessionState] = {}
        self.session_timeout_minutes = session_timeout_minutes

    def create_session(self, user_id: Optional[str] = None) -> SessionState:
        """创建一个新会话"""
        session = SessionState(
            session_id=str(uuid.uuid4())[:12],
            user_id=user_id,
        )
        self.sessions[session.session_id] = session
        return session

    def get_session(self, session_id: str) -> Optional[SessionState]:
        """获取会话状态"""
        session = self.sessions.get(session_id)
        if session is None:
            return None

        # 检查会话是否超时
        last_activity = datetime.fromisoformat(session.last_activity_at)
        elapsed = (datetime.now() - last_activity).total_seconds() / 60
        if elapsed > self.session_timeout_minutes:
            self.close_session(session_id)
            return None

        return session

    def close_session(self, session_id: str):
        """关闭会话"""
        session = self.sessions.pop(session_id, None)
        if session:
            # 将会话归档(实际场景中会持久化到数据库)
            self._archive_session(session)

    def _archive_session(self, session: SessionState):
        """归档会话(此处简化为打印统计)"""
        print(f"会话 {session.session_id} 已归档。"
              f"共{session.conversation_turns}轮对话,"
              f"完成{len(session.task_history)}个任务。")

    def start_task(self, session: SessionState, description: str) -> TaskState:
        """开始一个新任务"""
        task = TaskState(
            task_id=str(uuid.uuid4())[:8],
            description=description,
            status=TaskStatus.IN_PROGRESS,
        )
        session.current_task = task
        session.current_topic = ConversationTopic.TASK_EXECUTION
        session.touch()
        return task

    def update_task(self, session: SessionState, 
                    status: Optional[TaskStatus] = None,
                    result: Optional[str] = None,
                    error: Optional[str] = None):
        """更新当前任务的状态"""
        if session.current_task is None:
            return

        task = session.current_task
        if status:
            task.status = status
        if result:
            task.result = result
        if error:
            task.error = error

        task.updated_at = datetime.now().isoformat()
        session.touch()

        # 如果任务结束,移入历史
        if task.status in (TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED):
            session.task_history.append(task)
            session.current_task = None
            session.current_topic = ConversationTopic.GENERAL_QUERY

    def add_confirmed_fact(self, session: SessionState, key: str, value: Any):
        """添加一个已确认的事实"""
        session.confirmed_facts[key] = value
        session.touch()

    def add_pending_confirmation(self, session: SessionState, item: str):
        """添加一个待确认项"""
        if session.current_task:
            session.current_task.pending_confirmations.append(item)
        session.touch()

    def resolve_confirmation(self, session: SessionState, item: str, 
                              confirmed: bool, value: Any = None):
        """解决一个待确认项"""
        if session.current_task and item in session.current_task.pending_confirmations:
            session.current_task.pending_confirmations.remove(item)
            if confirmed and value is not None:
                session.add_confirmed_fact(session, item, value)
        session.touch()

    def get_context_summary(self, session: SessionState) -> str:
        """生成会话状态的文本摘要(用于注入LLM上下文)"""
        lines = []

        # 当前话题
        lines.append(f"当前话题:{session.current_topic.value}")

        # 当前任务
        if session.current_task:
            task = session.current_task
            lines.append(f"当前任务:{task.description}(状态:{task.status.value})")
            if task.subtasks_completed:
                lines.append(f"已完成子任务:{', '.join(task.subtasks_completed)}")
            if task.pending_confirmations:
                lines.append(f"待确认:{', '.join(task.pending_confirmations)}")
            if task.pending_inputs:
                lines.append(f"待输入:{', '.join(task.pending_inputs)}")

        # 已确认的事实
        if session.confirmed_facts:
            facts = [f"{k}={v}" for k, v in session.confirmed_facts.items()]
            lines.append(f"已确认信息:{';'.join(facts)}")

        # 未解决的问题
        if session.unresolved_questions:
            lines.append(f"待解决的问题:{', '.join(session.unresolved_questions)}")

        return "\n".join(lines) if lines else "(无特殊上下文)"

第三步:集成到MemorySystem中

修改MemorySystem,加入会话状态管理的支持:

class MemorySystem:
    """完整的记忆系统:短期 + 长期 + 会话状态"""

    def __init__(self, system_prompt: str,
                 long_term_memory: Optional[LongTermMemory] = None,
                 state_manager: Optional[SessionStateManager] = None):
        self.system_prompt = system_prompt
        self.state_manager = state_manager or SessionStateManager()
        self.long_term = long_term_memory
        self.extractor = MemoryExtractor()

        self.session: Optional[SessionState] = None
        self.short_term: Optional[WorkingMemory] = None
        self.messages: List[Dict[str, Any]] = []

    def initialize(self, user_input: str):
        """初始化一次新的会话"""
        # 创建或恢复会话
        self.session = self.state_manager.create_session()

        # 初始化短期记忆
        self.short_term = WorkingMemory(max_tokens=8000)
        self.short_term.set_system_prompt(self.system_prompt)

        # 添加用户输入
        self.short_term.add_user_message(user_input)

        # 注入长期记忆(如5.2节所述)
        if self.long_term:
            relevant = self.long_term.retrieve(user_input, top_k=3)
            if relevant:
                context_text = self.long_term.retrieve_as_context(user_input, top_k=3)
                self.short_term.add_user_message(
                    f"[系统注入了相关的历史记忆]\n{context_text}"
                )

        # 注入会话状态摘要
        state_summary = self.state_manager.get_context_summary(self.session)
        if state_summary != "(无特殊上下文)":
            self.short_term.add_user_message(
                f"[当前会话状态]\n{state_summary}"
            )

        self.messages = self.short_term.get_context()

    def continue_conversation(self, user_input: str):
        """继续当前会话(多轮对话的后续轮次)"""
        if self.session is None:
            self.initialize(user_input)
            return

        # 分析用户输入,更新会话状态
        self._update_state_from_user_input(user_input)

        # 添加用户消息到短期记忆
        self.short_term.add_user_message(user_input)

        # 注入更新后的会话状态摘要
        state_summary = self.state_manager.get_context_summary(self.session)
        if state_summary != "(无特殊上下文)":
            self.short_term.add_user_message(
                f"[更新后的会话状态]\n{state_summary}"
            )

        self.messages = self.short_term.get_context()

    def _update_state_from_user_input(self, user_input: str):
        """从用户输入中分析并更新会话状态"""
        if self.session is None:
            return

        # 检测后续跟进类输入
        followup_patterns = [
            "那", "然后", "还有", "另外", "顺便", 
            "如果", "能不能", "再", "也",
        ]

        is_followup = any(user_input.startswith(p) for p in followup_patterns)

        if is_followup and self.session.current_task:
            # 这是对当前任务的跟进
            self.session.current_topic = ConversationTopic.FOLLOW_UP
        elif is_followup and self.session.task_history:
            # 可能涉及之前完成的任务
            self.session.current_topic = ConversationTopic.FOLLOW_UP
        elif "?" in user_input or "吗" in user_input or "什么" in user_input:
            # 可能是澄清问题
            self.session.current_topic = ConversationTopic.GENERAL_QUERY
        else:
            # 可能是一个新任务
            self.session.current_topic = ConversationTopic.TASK_EXECUTION

        self.session.touch()

    def add_tool_result(self, result):
        """添加工具结果,并更新会话状态"""
        self.short_term.add_tool_result(
            getattr(result, 'tool_call_id', ""),
            result.content
        )

        # 从工具结果中提取确认的事实
        if self.session and getattr(result, 'success', False):
            tool_name = getattr(result, 'tool_name', '')
            if tool_name:
                self.state_manager.add_confirmed_fact(
                    self.session,
                    f"last_{tool_name}_result",
                    result.content[:100],
                )

        self.messages = self.short_term.get_context()

    def get_context(self) -> List[Dict[str, Any]]:
        """获取当前上下文"""
        return self.short_term.get_context() if self.short_term else []

处理对话中的指代消解

多轮对话中最大的挑战之一是指代——用户说"它"、"那个"、"这个"时,指的是什么?

用户:帮我查一下ORD12345678的物流状态
Agent:订单已发货,预计周五送达。

用户:那它现在到哪了?    ← "它"指的是"ORD12345678"

处理指代消解的策略:在会话状态中维护"当前讨论对象"。

class SessionStateManager:
    def __init__(self, session_timeout_minutes: int = 30):
        # ... 之前的初始化 ...
        self.current_subject: Dict[str, Any] = {}

    def set_current_subject(self, session: SessionState, 
                            subject_type: str, subject_id: str,
                            description: str):
        """设置当前讨论的对象"""
        session.metadata["current_subject"] = {
            "type": subject_type,
            "id": subject_id,
            "description": description,
        }
        session.touch()

    def get_current_subject(self, session: SessionState) -> Optional[Dict[str, Any]]:
        """获取当前讨论的对象"""
        return session.metadata.get("current_subject")

    def resolve_reference(self, session: SessionState, 
                          user_input: str) -> Optional[Dict[str, Any]]:
        """尝试解析用户输入中的指代"""
        # 检测指代词
        reference_words = ["它", "那个", "这个", "那", "这"]
        has_reference = any(word in user_input for word in reference_words)

        if has_reference:
            return self.get_current_subject(session)

        return None

在MemorySystem中集成指代消解:

def _update_state_from_user_input(self, user_input: str):
    """更新会话状态,包含指代消解"""
    if self.session is None:
        return

    # 尝试解析指代
    reference = self.state_manager.resolve_reference(self.session, user_input)
    if reference:
        # 用户使用了指代,将当前讨论对象注入到输入中
        augmented_input = (
            f"[指代解析:用户说的'{user_input}'中的指代词可能指的是:"
            f"{reference['description']}({reference['type']}:{reference['id']})]\n"
            f"原始输入:{user_input}"
        )
        self.short_term.add_user_message(augmented_input)
    else:
        self.short_term.add_user_message(user_input)

    # ... 其他状态更新逻辑 ...

处理话题切换

多轮对话中,用户可能突然切换话题:

用户:帮我查一下明天的天气
Agent:明天北京晴。

用户:对了,帮我创建个任务,周五前交周报

Agent需要识别话题的切换,并更新会话状态。如果Agent还在"天气"话题的上下文中理解"任务创建",可能会产生困惑。


class TopicDetector:
    """话题切换检测器"""

    def __init__(self):
        self.topic_keywords = {
            "weather": ["天气", "下雨", "温度", "晴", "阴"],
            "task": ["任务", "创建", "待办", "完成"],
            "calendar": ["日程", "会议", "安排", "日历"],
            "message": ["发送", "通知", "消息", "告诉"],
            "search": ["搜索", "查找", "文档", "资料"],
        }

    def detect_topic(self, user_input: str) -> str:
        """检测用户输入的话题"""
        scores = {}
        for topic, keywords in self.topic_keywords.items():
            score = sum(1 for kw in keywords if kw in user_input)
            if score > 0:
                scores[topic] = score

        if not scores:
            return "general"

        return max(scores, key=scores.get)

    def is_topic_switch(self, previous_topic: str, current_topic: str) -> bool:
        """判断是否发生了话题切换"""
        if previous_topic == "general" or current_topic == "general":
            return False
        return previous_topic != current_topic

多轮对话的完整流程示例 以下是一个完整的对话流程,展示会话状态如何帮助Agent维持连续性:

【第1轮】
用户:帮我查一下ORD12345678的物流状态
状态更新:创建任务"查询订单ORD12345678的物流状态"(IN_PROGRESS)
Agent调用:check_order_status(order_id="ORD12345678")
工具返回:订单已发货,当前在杭州中转站,预计明天送达北京
状态更新:任务完成(COMPLETED),设置current_subject={type:"order", id:"ORD12345678"}
Agent回答:您的订单已发货,目前在杭州中转站,预计明天送达北京。

【第2轮】
用户:那它到北京之后什么时候派送?
指代解析:"它"→当前讨论对象ORD12345678
状态更新:话题=FOLLOW_UP,当前任务=查询派送时间
Agent调用:get_delivery_estimate(order_id="ORD12345678")
工具返回:预计明天上午派送
Agent回答:预计明天上午开始派送。

【第3轮】
用户:明天上午我要开会,帮我查一下明天上午的日程
话题检测:从"order"切换到"calendar"
状态更新:话题切换,创建新任务
Agent调用:query_calendar(start_date="tomorrow", end_date="tomorrow")
工具返回:明天上午10:00-11:30有产品评审会
Agent回答:明天上午10:00-11:30有产品评审会。如果您需要调整派送时间,可以联系快递员。

注意Agent在第3轮的表现:它没有忘记之前关于订单的讨论,在回答日程后还提到了派送时间的关联。这种"全局意识"正是会话状态管理的价值。

会话状态的持久化

会话状态不应只存在内存中。如果Agent服务重启,所有会话状态都丢失,用户会突然发现Agent"失忆"了。

import json
import os

class PersistentSessionStore:
    """持久化的会话存储"""

    def __init__(self, storage_dir: str = "./session_storage"):
        self.storage_dir = storage_dir
        os.makedirs(storage_dir, exist_ok=True)

    def save(self, session: SessionState):
        """保存会话到磁盘"""
        filepath = os.path.join(self.storage_dir, f"{session.session_id}.json")
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(session_to_dict(session), f, ensure_ascii=False, indent=2)

    def load(self, session_id: str) -> Optional[SessionState]:
        """从磁盘加载会话"""
        filepath = os.path.join(self.storage_dir, f"{session_id}.json")
        if not os.path.exists(filepath):
            return None
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return session_from_dict(data)

    def delete(self, session_id: str):
        """删除会话"""
        filepath = os.path.join(self.storage_dir, f"{session_id}.json")
        if os.path.exists(filepath):
            os.remove(filepath)


def session_to_dict(session: SessionState) -> Dict[str, Any]:
    """将会话状态序列化为字典"""
    # 简化的序列化
    return {
        "session_id": session.session_id,
        "user_id": session.user_id,
        "started_at": session.started_at,
        "last_activity_at": session.last_activity_at,
        "current_topic": session.current_topic.value,
        "confirmed_facts": session.confirmed_facts,
        "conversation_turns": session.conversation_turns,
        "metadata": session.metadata,
    }

本节小结

会话状态管理让Agent在多轮对话中保持连续性。本节讨论了:

  • 会话状态的三个层次:会话层、任务层、消息层
  • 任务跟踪:当前任务、已完成子任务、待确认项
  • 指代消解:将"它"、"那个"解析为具体的对象
  • 话题切换检测:识别用户是否转移了话题
  • 状态持久化:跨重启保持会话状态

核心原则:会话状态管理的本质是让Agent知道"我们在聊什么、聊到哪了、还需要什么"。它不是替代对话历史,而是为对话历史提供结构化的元信息,帮助LLM更准确地理解上下文。

在下一节中,我们将讨论记忆压缩与摘要策略——当上下文窗口面临压力时,如何智能地压缩历史信息,保持Agent的推理质量。

本教程共32节,当前为第23节!
本教程最新修订时间为:2026-09-30 09:02:04

📌 面试天下网:一款服务于上班族的口袋书,让大家在地铁里/公交上可以学习大模型技术!
📌 网站公告:【悬赏 200 元/篇】寻找“大模型面试战场”的一手回忆录
📌 网站公告:【大模型/Agent面试陪练1V1指导】正式启动......

评论专区

评论加载中...