AI Agent 框架探秘:拆解 OpenHands(5)--- 交互&会话
目录
- [AI Agent 框架探秘:拆解 OpenHands(5)--- 交互&会话](#AI Agent 框架探秘:拆解 OpenHands(5)--- 交互&会话)
- [0x00 概述](#0x00 概述)
- [0x01 背景](#0x01 背景)
- [1.1 会话的意义](#1.1 会话的意义)
- [1.2 会话系统的常见功能](#1.2 会话系统的常见功能)
- [1.3 Session 常见内容](#1.3 Session 常见内容)
- [1.4 会话生命周期](#1.4 会话生命周期)
- [1.5 前文回顾](#1.5 前文回顾)
- [0x02 OpenHands 会话系统](#0x02 OpenHands 会话系统)
- [2.1 对话管理接口 ConversationManager](#2.1 对话管理接口 ConversationManager)
- [2.1.1 StandaloneConversationManager](#2.1.1 StandaloneConversationManager)
- [2.1.2 Session初始化](#2.1.2 Session初始化)
- [2.2 session.py(WebSession)](#2.2 session.py(WebSession))
- [2.2.1 WebSession 类概述](#2.2.1 WebSession 类概述)
- [2.2.2 WebSession 核心属性](#2.2.2 WebSession 核心属性)
- [2.2.3 主要功能模块](#2.2.3 主要功能模块)
- [2.2.4 与系统其他组件的关系](#2.2.4 与系统其他组件的关系)
- [2.2.5 初始化 AgentSession](#2.2.5 初始化 AgentSession)
- [2.3 agent_session](#2.3 agent_session)
- [2.4 用户交互(oh_user_action)逻辑](#2.4 用户交互(oh_user_action)逻辑)
- [2.4.1 用户发消息](#2.4.1 用户发消息)
- [2.4.2 事件添加](#2.4.2 事件添加)
- [2.4.3 事件处理](#2.4.3 事件处理)
- [2.1 对话管理接口 ConversationManager](#2.1 对话管理接口 ConversationManager)
- [0xFF 参考](#0xFF 参考)
0x00 概述
有意义的多轮对话要求智能体能够理解上下文。就像人类一样,智能体需要记住对话历史:已经说过和做过什么,以保持连贯性并避免重复。
以下是OpenHands Applications的示例图,本篇就来看看会话和交互如何进行。
因为本系列借鉴的文章过多,可能在参考文献中有遗漏的文章,如果有,还请大家指出。
0x01 背景
本节基于 Google ADK 来进行背景介绍。
1.1 会话的意义
就像你不会每次发短信都从头开始一样,智能体也需要了解当前交互的上下文。常见的Agent系统会通过 Session、State 和 Memory 提供了结构化的上下文管理方式。
Session:当前对话线程(可以将你与智能体的不同对话实例视为独立的对话线程 ,它们可能会利用长期知识 )- 表示用户与你的智能体系统之间单次、持续的交互。
- 包含该特定交互期间,智能体采取的消息和动作(称为
Events)的时间顺序序列。 - 一个
Session还可以保存仅在本次对话 期间相关的临时数据(State)。
State:当前对话中的数据- 存储在特定
Session内的数据。 - 用于管理仅 与当前(单次)、活跃 对话线程相关的信息(例如,本次对话 中的购物车商品,本 Session 中提到的用户偏好)。
- 关注如何高效地读取、写入和管理 session 专属数据。
- 存储在特定
Memory:可检索的跨 Session 信息- 表示可能跨越多个过去 Session或包含外部数据源的信息存储。
- 它作为一个知识库,智能体可以检索以回忆超出当前对话的信息或上下文。
因此,Agent系统一般有如下两套组件或者服务:
SessionService:管理不同的对话线程(Session对象)负责生命周期管理:创建、检索、更新(追加Events、修改State)和删除单个Session。MemoryService:管理长期知识存储(Memory),负责将信息(通常来自已完成的Session)导入长期存储。提供基于查询检索已存储知识的方法。
本篇介绍对话服务,后续另外介绍内存服务。
1.2 会话系统的常见功能
用户通常不会直接创建或管理 Session 对象,而是通过 SessionService。该服务作为会话生命周期的中央管理者。其核心职责包括:
- 开启新对话: 当用户开始交互时,创建新的
Session对象。 - 恢复已有对话: 通过 ID 检索特定
Session,让智能体可以从上次中断处继续。 - 保存进度: 将新的交互(
Event对象)追加到 session 历史。这也是 sessionstate更新的机制(详见State章节)。 - 列出对话: 查找特定用户和应用的活跃会话线程。
- 清理: 当对话结束或不再需要时,删除
Session及其相关数据。
选择合适的 SessionService 是决定智能体对话历史和临时数据如何存储与持久化的关键。
1.3 Session 常见内容
一般来说,当用户开始与智能体交互时,SessionService 会创建一个 Session 对象。该对象作为单个对话线程相关所有内容的容器。其主要属性如下:
- 标识信息(
id,appName,userId):
用于唯一标记对话的核心字段,具体说明如下:
id:当前对话线程的唯一标识符,是后续检索该对话的关键依据。一个SessionService对象可管理多个Session(会话)实例,此字段用于明确当前操作对应的具体会话对象。示例值:"test_id_modification"。app_name:标识当前对话所属的智能体应用。示例值:"id_modifier_workflow"。userId:将对话与特定用户关联的关联字段,用于用户维度的对话管理与权限控制。
- 对话历史(
events):
按时间顺序排列的交互序列,包含当前对话线程中发生的所有交互行为(以 Event 对象形式存储),涵盖用户消息、智能体响应、工具调用动作等全量交互记录。
- 会话状态(
state):
用于存储仅与当前活跃对话相关的临时数据 ,相当于智能体在交互过程中的 "临时草稿本"。下一节将详细介绍 state 的具体使用与管理方式。
- 活动追踪(
lastUpdateTime):
时间戳字段,记录当前对话线程中最后一次交互事件的发生时间,用于会话活跃度判断与过期管理。
1.4 会话生命周期
以下是 Session 与 SessionService 在一次对话轮次中协作的简化流程:
- 开始或恢复: 你的应用程序需要使用
SessionService来要么create_session(用于新聊天),要么使用现有的 session id。 - 提供上下文:
Runner从适当的服务方法获取相应的Session对象,为智能体提供对相应 Session 的state和events的访问权限。 - 智能体处理: 用户用查询提示智能体。智能体分析查询以及可能的 session
state和events历史来确定响应。 - 响应和状态更新: 智能体生成响应(并可能标记要在
state中更新的数据)。Runner将其打包为Event。 - 保存交互:
Runner调用sessionService.append_event(session, event),将session和新的event作为参数。服务将Event添加到历史记录中,并根据事件中的信息更新存储中的 sessionstate。session 的last_update_time也会得到更新。 - 准备下一次: 智能体的响应发送给用户。更新后的
Session现在由SessionService存储,准备进行下一轮(这通常会在当前会话中继续对话,从步骤 1 重新开始循环)。 - 结束对话: 当对话结束时,你的应用程序调用
sessionService.delete_session(...)来清理存储的会话数据(如果不再需要的话)。
此循环突出显示了 SessionService 如何通过管理每个 Session 对象的历史和状态,确保对话的连续性。
1.5 前文回顾
我们首先回顾前文介绍的,OpenHands项目关于对话的服务器端组件。
session.py文件定义了Session类,它代表与客户端的WebSocket会话。agent_session.py文件包含AgentSession类,它管理会话内Agent的生命周期。conversation_manager.py文件定义了ConversationManager类,它负责管理多个客户端会话。listen.py文件是主服务器文件,它设置FastAPI应用程序并定义各种API端点。此处关键一步为与会话管理器 ConversationManager 建立连接。
以上几步展示了服务器组件如何构建会话,因此我们由此而入。
0x02 OpenHands 会话系统
会话是专门设计用于跟踪和管理单独对话线程的对象。会话就可以理解为AI代理的临时工作空间,就像你为特定项目准备的办公桌。它包含当前对话的所有必要工具、笔记和参考资料,一切都是即时可访问的,但也具有临时性和任务特定性。
具体而言,在OpenHands中:
WebSession是一个 Web 服务器绑定的会话包装器,负责管理单个 Web 客户端连接并协调 AgentSession 生命周期。是 OpenHands 系统中连接前端用户界面和后端Agent执行的核心桥梁,负责协调整个交互流程。AgentSession是 OpenHands 框架中 Agent运行的 "上下文容器",核心作用是封装Agent执行所需的所有组件(Agent、控制器、运行时、内存、事件流),统一管理它们的生命周期(初始化、启动、通信、关闭),并提供会话级的配置隔离、数据持久化和状态管理,是Agent能够独立、稳定执行任务的基础。
2.1 对话管理接口 ConversationManager
ConversationManager 类定义了对话管理的接口,适用于单机模式和集群模式。它负责处理对话的全生命周期,
包括创建、附加、分离和清理。这是OpenHands的一个扩展点,基于它构建的应用可通过服务器配置修改行为,无需改动其核心代码。应用程序可通过以下方式提供自定义实现:
- 创建一个继承自ConversationManager的类
- 实现所有必需的抽象方法
- 将server_config.conversation_manager_class设置为该实现类的全限定名
ConversationManager 定义如下。
python
class ConversationManager(ABC):
"""OpenHands中对话管理的抽象基类。
应用程序可能需要在以下场景中自定义实现:
- 具有分布式对话状态的集群部署
- 自定义持久化或缓存策略
- 与外部对话管理系统集成
- 增强的监控或日志能力
实现类通过openhands.server.shared.py中的get_impl()方法实例化。
"""
sio: socketio.AsyncServer # Socket.IO异步服务器实例,用于实时通信
config: OpenHandsConfig # OpenHands配置对象,存储系统参数
file_store: FileStore # 文件存储实例,用于管理对话相关文件
conversation_store: ConversationStore # 对话存储实例,用于持久化对话数据
2.1.1 StandaloneConversationManager
StandaloneConversationManager 是ConversationManager的子类。是默认实现,适用于单服务器部署场景。
python
@dataclass
class StandaloneConversationManager(ConversationManager):
"""Default implementation of ConversationManager for single-server deployments.
See ConversationManager for extensibility details.
"""
sio: socketio.AsyncServer
config: OpenHandsConfig
file_store: FileStore
server_config: ServerConfig
# Defaulting monitoring_listener for temp backward compatibility.
monitoring_listener: MonitoringListener = MonitoringListener()
_local_agent_loops_by_sid: dict[str, Session] = field(default_factory=dict)
_local_connection_id_to_session_id: dict[str, str] = field(default_factory=dict)
_active_conversations: dict[str, tuple[ServerConversation, int]] = field(
default_factory=dict
)
_detached_conversations: dict[str, tuple[ServerConversation, float]] = field(
default_factory=dict
)
_conversations_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
_cleanup_task: asyncio.Task | None = None
_conversation_store_class: type[ConversationStore] | None = None
_loop: asyncio.AbstractEventLoop | None = None
2.1.2 Session初始化
Session初始化的流程图如下。
StandaloneConversationManager 的 join_conversation 函数如下,其调用了 maybe_start_agent_loop 初始化Agent。需要注意,此处 Session 是 WebSession。
python
from openhands.server.session.session import WebSession as Session
async def join_conversation(
self,
sid: str,
connection_id: str,
settings: Settings,
user_id: str | None,
) -> AgentLoopInfo:
await self.sio.enter_room(connection_id, ROOM_KEY.format(sid=sid))
self._local_connection_id_to_session_id[connection_id] = sid
# 此处调用 maybe_start_agent_loop
agent_loop_info = await self.maybe_start_agent_loop(sid, settings, user_id)
return agent_loop_info
maybe_start_agent_loop 调用了 _start_agent_loop 初始化 Session。
python
class ConversationManager:
def __init__(self, config: OpenHandsConfig, sio: Any, file_store: Any):
self.config = config # 框架全局配置
self.sio = sio # SocketIO实例(用于客户端通信)
self.file_store = file_store # 文件存储实例(用于会话数据持久化)
self._local_agent_loops_by_sid: Dict[str, Session] = {} # 会话ID到Session实例的映射(缓存活跃会话)
self._loop = asyncio.get_event_loop() # 事件循环实例
async def maybe_start_agent_loop(
self,
sid: str, # 会话ID(唯一标识一个对话)
settings: Settings, # 用户/会话设置(含Agent类型、LLM配置等)
user_id: Optional[str] = None, # 用户ID(可选,用于用户级并发控制)
initial_user_msg: Optional[MessageAction] = None, # 初始用户消息(可选,会话启动时的第一条消息)
replay_json: Optional[str] = None, # 回放JSON字符串(可选,用于会话回放场景)
) -> AgentLoopInfo:
"""
尝试启动Agent循环:优先复用已存在的会话,不存在则新建。
核心逻辑:
- 检查会话ID对应的会话是否已存在(缓存于_local_agent_loops_by_sid)
- 存在则直接返回会话信息,不存在则调用_start_agent_loop新建会话
- 返回标准化的Agent循环信息(供外部调用者使用)
"""
# 从缓存中获取会话(复用已有会话,避免重复初始化)
session = self._local_agent_loops_by_sid.get(sid)
if not session:
# 会话不存在,新建Agent循环
session = await self._start_agent_loop(
sid, settings, user_id, initial_user_msg, replay_json
)
# 将Session实例转换为标准化的AgentLoopInfo返回
return self._agent_loop_info_from_session(session)
_start_agent_loop 该代码是 OpenHands 框架中 Agent循环(Agent Loop)的启动与管理核心,负责会话的创建、复用、并发控制和初始化,是连接用户请求与Agent执行的关键枢纽。其核心使命是:在遵守并发限制的前提下,为用户会话提供完整的组件初始化(LLM 注册表、统计、事件订阅),确保Agent能够顺畅启动并运行。
_start_agent_loop 的核心特色如下:
- 会话复用机制 :通过
_local_agent_loops_by_sid缓存活跃会话,避免重复初始化,提升响应速度和资源利用率(例如用户重新连接同一会话时直接复用)。 - 智能并发控制:基于用户 ID 限制最大并发会话数,超限后自动关闭最早的会话,同时向客户端发送友好通知,平衡资源占用与用户体验。
- 组件化初始化 :整合
create_registry_and_conversation_stats函数,一键完成 LLM 注册表、对话统计、配置适配三大核心组件的初始化,架构清晰且解耦。 - 异步非阻塞设计 :Agent初始化(
session.initialize_agent)通过asyncio.create_task异步执行,不阻塞会话创建流程,提升系统吞吐量。 - 事件驱动扩展:自动订阅会话事件流,通过回调函数响应会话更新,支持后续扩展监控、统计等功能,具备良好的可扩展性。
- 容错与兼容性 :处理重复订阅异常,避免报错;支持会话回放(
replay_json)和初始消息(initial_user_msg),适配正常对话、回放等多种场景。
_start_agent_loop 代码如下。
python
async def _start_agent_loop(
self,
sid: str,
settings: Settings,
user_id: Optional[str] = None,
initial_user_msg: Optional[MessageAction] = None,
replay_json: Optional[str] = None,
) -> Session:
"""
内部方法:实际创建并启动Agent循环,包含并发控制、会话初始化、事件订阅等核心流程。
"""
# 1. 并发会话数量控制:检查用户当前活跃会话数是否超过上限
# 获取用户当前运行中的所有会话ID
running_session_ids = await self.get_running_agent_loops(user_id)
# 若超过最大并发数,关闭最早的会话以释放资源
if len(running_session_ids) >= self.config.max_concurrent_conversations:
# 获取用户的会话存储实例,读取所有活跃会话的元数据
conversation_store = await self._get_conversation_store(user_id)
conversations = await conversation_store.get_all_metadata(running_session_ids)
# 按最后更新时间排序(最新的在前, oldest的在后)
conversations.sort(key=_last_updated_at_key, reverse=True)
# 循环关闭最早的会话,直到并发数符合限制
while len(conversations) >= self.config.max_concurrent_conversations:
oldest_conversation = conversations.pop() # 取出最早的会话
oldest_sid = oldest_conversation.conversation_id
# 向客户端发送错误通知(告知会话已关闭)
status_update = {
'status_update': True,
'type': 'error',
'id': 'AGENT_ERROR$TOO_MANY_CONVERSATIONS',
'message': '同时开启的会话数已达上限。若仍需使用该会话,可发送消息重新激活Agent',
}
# 在事件循环中发送SocketIO事件(定向到该会话的房间)
await run_in_loop(
self.sio.emit(
'oh_event',
status_update,
to=ROOM_KEY.format(sid=oldest_sid), # 按会话ID定向发送
),
self._loop,
)
# 关闭最早的会话(释放资源)
await self.close_session(oldest_sid)
# 2. 初始化核心组件:LLM注册表、对话统计、最终配置
llm_registry, conversation_stats, final_config = (
create_registry_and_conversation_stats(self.config, sid, user_id, settings)
)
# 3. 创建Session实例(封装会话的所有状态和组件)
session = Session(
sid=sid,
file_store=self.file_store, # 绑定文件存储
config=final_config, # 绑定最终配置
llm_registry=llm_registry, # 绑定LLM注册表
conversation_stats=conversation_stats, # 绑定对话统计
sio=self.sio, # 绑定SocketIO实例
user_id=user_id, # 绑定用户ID
)
# 4. 将新会话缓存到本地(供后续复用)
self._local_agent_loops_by_sid[sid] = session
# 5. 异步初始化Agent(不阻塞当前流程):加载Agent、处理初始消息、回放会话(若有)
asyncio.create_task(
session.initialize_agent(settings, initial_user_msg, replay_json)
)
# 6. 订阅会话事件流:监听会话更新事件(仅新建会话时订阅,复用会话跳过)
try:
session.agent_session.event_stream.subscribe(
subscriber=EventStreamSubscriber.SERVER, # 订阅者类型:服务器
callback=self._create_conversation_update_callback(
user_id, sid, settings, llm_registry # 会话更新回调函数
),
callback_id=UPDATED_AT_CALLBACK_ID, # 回调ID(用于后续取消订阅)
)
except ValueError:
# 若已存在相同ID的订阅,忽略该操作(避免重复订阅)
# 返回创建好的Session实例
return session
2.2 session.py(WebSession)
session.py 定义了 WebSession 类,它是 OpenHands 系统中管理 Web 客户端会话的核心组件。
2.2.1 WebSession 类概述
WebSession 是一个 Web 服务器绑定的会话包装器,负责管理单个 Web 客户端连接并协调 AgentSession 生命周期。WebSession 的关键设计模式为:
- 异步队列模式:使用 asyncio.Queue 管理事件发布,确保非阻塞操作
- 事件驱动架构:通过事件订阅/发布机制实现组件解耦
- 状态管理模式:跟踪会话状态和连接状态
- 错误处理机制:全面的异常捕获和错误报告
WebSession 是 OpenHands 系统中连接前端用户界面和后端Agent执行的核心桥梁,负责协调整个交互流程。
2.2.2 WebSession 核心属性
WebSession 核心属性为:
- sid:稳定的会话 ID,跨传输保持一致
- sio:Socket.IO 服务器,用于向 Web 客户端发送事件
- agent_session:核心Agent会话,协调运行时和 LLM
- config:有效的 OpenHands 配置
- llm_registry:负责 LLM 访问和重试钩子的注册表
- file_store:会话的文件存储接口
- user_id:可选的多租户用户标识符
WebSession会订阅 EventStreamSubscriber.SERVER。
python
class WebSession:
"""Web server-bound session wrapper.
This was previously named `Session`. We keep `Session` as a compatibility alias
(see openhands.server.session.__init__) so downstream imports/tests continue to
work. The class manages a single web client connection and orchestrates the
AgentSession lifecycle for that conversation.
"""
sid: str
sio: socketio.AsyncServer | None
last_active_ts: int = 0
is_alive: bool = True
agent_session: AgentSession
loop: asyncio.AbstractEventLoop
config: OpenHandsConfig
llm_registry: LLMRegistry
file_store: FileStore
user_id: str | None
logger: LoggerAdapter
2.2.3 主要功能模块
WebSession 的主要功能模块为:
- 初始化和配置管理
- init 方法设置会话的基本配置和组件
- 订阅 EventStream 的 SERVER 事件
- 初始化异步事件发布队列
- Agent初始化(initialize_agent)
- 配置Agent、LLM 和运行时环境
- 处理 MCP(Model Context Protocol)配置
- 设置 condenser(压缩器)配置
- 启动 AgentSession
- 错误处理和验证
- 事件处理(on_event 和 _on_event)
- 处理来自Agent的事件
- 过滤 NullAction 和 NullObservation
- 根据事件源决定如何处理和转发事件
- 将环境反馈作为Agent事件发送给 UI
- 处理来自Agent的事件
- 消息分发(dispatch)
- 处理来自用户的事件
- 验证图像支持
- 将事件添加到事件流中
- 异步消息发送(send, _monitor_publish_queue, _send)
- 使用队列机制异步发送消息
- 确保 WebSocket 连接稳定后再发送
- 处理连接状态和错误
- 状态管理
- close 方法清理会话资源
- queue_status_message 和 _send_status_message 处理状态更新消息
2.2.4 与系统其他组件的关系
WebSession 与系统其他组件的关系如下:
-
与 EventStream 集成
- 作为 SERVER 订阅者接收事件
- 处理来自Agent和用户的事件流
-
与 AgentSession 协作:
- 管理 AgentSession 生命周期
- 转发用户事件到Agent
- 将Agent响应发送给客户端
-
与 Socket.IO 集成:
- 使用 Socket.IO 向客户端发送实时事件
- 管理 WebSocket 连接状态
2.2.5 初始化 AgentSession
WebSession 的初始化会中完成对AgentSession的初始化,AgentSession的初始化中又做了EventStream的初始化,所以整个会话的EventStream也就是在这里创建的,这里在事件流中订阅了EventStreamSubscriber.SERVER,事件回调函数中将需要向前端广播的事件通过socket进行发送。
python
class WebSession:
def __init__(
self,
sid: str,
config: OpenHandsConfig,
llm_registry: LLMRegistry,
conversation_stats: ConversationStats,
file_store: FileStore,
sio: socketio.AsyncServer | None,
user_id: str | None = None,
):
self.sid = sid
self.sio = sio
self.last_active_ts = int(time.time())
self.file_store = file_store
self.logger = OpenHandsLoggerAdapter(extra={'session_id': sid})
self.llm_registry = llm_registry
self.conversation_stats = conversation_stats
self.agent_session = AgentSession(
sid,
file_store,
llm_registry=self.llm_registry,
conversation_stats=conversation_stats,
status_callback=self.queue_status_message,
user_id=user_id,
)
self.agent_session.event_stream.subscribe(
EventStreamSubscriber.SERVER, self.on_event, self.sid
)
self.config = config
# Lazy import to avoid ircular dependency
from openhands.experiments.experiment_manager import ExperimentManagerImpl
self.config = ExperimentManagerImpl.run_config_variant_test(
user_id, sid, self.config
)
self.loop = asyncio.get_event_loop()
self.user_id = user_id
self._publish_queue: asyncio.Queue = asyncio.Queue()
self._monitor_publish_queue_task: asyncio.Task = self.loop.create_task(
self._monitor_publish_queue()
)
self._wait_websocket_initial_complete: bool = True
agent_session.start()中完成了security_analyzer、runtime、memory、controller的初始化。
2.3 agent_session
2.3.1 AgentSession
AgentSession 是 OpenHands 框架中 Agent运行的 "上下文容器",核心作用是封装Agent执行所需的所有组件(Agent、控制器、运行时、内存、事件流),统一管理它们的生命周期(初始化、启动、通信、关闭),并提供会话级的配置隔离、数据持久化和状态管理,是Agent能够独立、稳定执行任务的基础。
AgentSession 核心特色如下:
- 全组件生命周期管理:集中初始化并关联Agent、控制器、运行时、内存、事件流等核心组件,确保组件间通信顺畅,生命周期一致(启动 / 关闭同步)。
- 灵活的环境配置:支持 Git 仓库集成、自定义密钥注入、MCP 工具扩展等,适配代码开发、第三方服务调用等复杂场景,满足多样化任务需求。
- 会话状态安全管控 :严格校验会话状态(避免重复启动、已关闭会话启动失败),通过状态标记(
_starting/_closed)确保流程安全性,减少异常。 - 支持会话回放与状态恢复 :提供
_run_replay接口支持从 JSON 数据恢复历史会话,便于调试、任务续跑和场景复现。 - 精细化日志与监控:集成带会话上下文的日志器,记录启动耗时、成功状态、状态恢复等元数据,便于问题排查和系统监控。
- 安全与隔离设计 :通过自定义密钥处理器(
UserSecrets)安全管理第三方密钥,运行时环境隔离执行代码,避免敏感信息泄露和系统风险。 - 状态驱动的事件机制:启动时根据是否有初始消息自动设置Agent状态(运行中 / 等待用户输入),并通过事件流同步状态,确保组件间状态一致性。
AgentSession 定义如下:
python
class AgentSession:
"""
Agent会话类:封装Agent运行的完整上下文,管理Agent、控制器、运行时、内存等核心组件的生命周期。
属性说明:
controller: Agent控制器实例(负责调度Agent执行流程)
sid: 会话唯一标识
user_id: 用户ID(可选)
event_stream: 事件流(组件间通信核心)
llm_registry: LLM注册表(管理LLM实例)
file_store: 文件存储(持久化会话数据)
runtime: 运行时环境(如沙盒,执行代码/命令)
memory: Agent内存(存储会话历史、上下文等)
_starting: 会话启动中标记
_started_at: 会话启动时间戳
_closed: 会话关闭标记
loop: 异步事件循环
logger: 带会话上下文的日志器
"""
sid: str
user_id: Optional[str]
event_stream: EventStream
llm_registry: LLMRegistry
file_store: FileStore
controller: Optional[AgentController] = None
runtime: Optional[Runtime] = None
memory: Optional[Memory] = None
_starting: bool = False
_started_at: float = 0
_closed: bool = False
loop: Optional[asyncio.AbstractEventLoop] = None
logger: LoggerAdapter
def __init__(
self,
sid: str,
file_store: FileStore,
llm_registry: LLMRegistry,
conversation_stats: ConversationStats,
status_callback: Optional[Callable] = None,
user_id: Optional[str] = None,
) -> None:
"""
初始化AgentSession实例。
参数:
sid: 会话ID(唯一标识)
file_store: 文件存储实例(用于事件流、内存数据持久化)
llm_registry: LLM注册表实例(提供LLM资源)
conversation_stats: 对话统计实例(记录会话相关统计数据)
status_callback: 状态回调函数(可选,会话状态变更时触发)
user_id: 用户ID(可选,用于用户级数据隔离)
"""
self.sid = sid
# 初始化事件流(会话内组件通信的核心枢纽)
self.event_stream = EventStream(sid, file_store, user_id)
self.file_store = file_store
self._status_callback = status_callback # 状态变更回调(如通知客户端)
self.user_id = user_id
# 初始化带会话上下文的日志器(便于追踪会话级日志)
self.logger = OpenHandsLoggerAdapter(
extra={'session_id': sid, 'user_id': user_id}
)
self.llm_registry = llm_registry # 绑定LLM注册表
self.conversation_stats = conversation_stats # 绑定对话统计实例
AgentSession 初始化完成后向事件流中添加ChangeAgentStateAction事件。
具体代码如下:
python
async def start(
self,
runtime_name: str, # 运行时名称(如"sandbox",指定运行环境类型)
config: OpenHandsConfig, # 框架全局配置
agent: Agent, # 已初始化的Agent实例
max_iterations: int, # Agent执行的最大迭代次数(防止无限循环)
git_provider_tokens: Optional[PROVIDER_TOKEN_TYPE] = None, # Git提供商令牌(如GitHub令牌)
custom_secrets: Optional[CUSTOM_SECRETS_TYPE] = None, # 自定义密钥(第三方服务访问用)
max_budget_per_task: Optional[float] = None, # 单任务最大预算(可选)
agent_to_llm_config: Optional[Dict[str, LLMConfig]] = None, # Agent-LLM配置映射
agent_configs: Optional[Dict[str, AgentConfig]] = None, # 所有Agent配置字典
selected_repository: Optional[str] = None, # 选中的Git仓库地址(可选)
selected_branch: Optional[str] = None, # 选中的仓库分支(可选)
initial_message: Optional[MessageAction] = None, # 初始用户消息(可选)
conversation_instructions: Optional[str] = None, # 会话指令(自定义Agent行为)
replay_json: Optional[str] = None, # 会话回放JSON数据(可选)
) -> None:
"""
启动Agent会话:初始化运行时、内存、控制器,触发Agent开始执行。
核心流程:
1. 校验会话状态(避免重复启动)
2. 创建运行时环境(如沙盒)
3. 配置Git令牌、自定义密钥
4. 创建Agent内存(存储上下文、会话指令等)
5. (可选)添加MCP工具到Agent
6. (可选)执行会话回放
7. 创建Agent控制器(调度Agent执行)
8. 发送初始事件(启动状态/等待用户输入)
"""
# 校验会话状态:已存在控制器或运行时 → 抛出异常(避免重复启动)
if self.controller or self.runtime:
raise RuntimeError(
'Session already started. You need to close this session and start a new one.'
)
# 会话已关闭 → 日志警告并返回
if self._closed:
self.logger.warning('Session closed before starting')
return
self._starting = True # 标记会话启动中
started_at = time.time()
self._started_at = started_at
finished = False # 执行完成标记(用于监控)
runtime_connected = False # 运行时连接成功标记
restored_state = False # 状态恢复标记(会话回放/恢复场景)
# 初始化自定义密钥处理器(管理第三方服务密钥)
custom_secrets_handler = UserSecrets(
custom_secrets=custom_secrets if custom_secrets else {}
)
try:
# 1. 创建运行时环境(如Docker沙盒)并连接
runtime_connected = await self._create_runtime(
runtime_name=runtime_name,
config=config,
agent=agent,
git_provider_tokens=git_provider_tokens,
custom_secrets=custom_secrets,
selected_repository=selected_repository,
selected_branch=selected_branch,
)
# 提取仓库目录名(若指定了Git仓库)
repo_directory = None
if self.runtime and runtime_connected and selected_repository:
repo_directory = selected_repository.split('/')[-1] # 从仓库地址提取目录名(如"openhands")
# 2. 配置Git提供商令牌(若有)
if git_provider_tokens:
provider_handler = ProviderHandler(provider_tokens=git_provider_tokens)
await provider_handler.set_event_stream_secrets(self.event_stream) # 注入令牌到事件流
# 3. 配置自定义密钥(若有)
if custom_secrets:
custom_secrets_handler.set_event_stream_secrets(self.event_stream) # 注入自定义密钥到事件流
# 4. 创建Agent内存(存储会话上下文、仓库信息、会话指令等)
self.memory = await self._create_memory(
selected_repository=selected_repository,
repo_directory=repo_directory,
selected_branch=selected_branch,
conversation_instructions=conversation_instructions,
custom_secrets_descriptions=custom_secrets_handler.get_custom_secrets_descriptions(), # 密钥描述(供Agent参考)
working_dir=config.workspace_mount_path_in_sandbox, # 沙盒中的工作目录路径
)
# 5. (可选)添加MCP工具到Agent(需运行时已连接且Agent启用MCP)
if self.runtime and runtime_connected and agent.config.enable_mcp:
await add_mcp_tools_to_agent(agent, self.runtime, self.memory)
# 6. (可选)执行会话回放(从replay_json恢复历史会话)
if replay_json:
initial_message = self._run_replay(
initial_message,
replay_json,
agent,
config,
max_iterations,
max_budget_per_task,
agent_to_llm_config,
agent_configs,
)
# 7. (正常场景)创建Agent控制器(调度Agent执行流程)
else:
self.controller, restored_state = self._create_controller(
agent,
config.security.confirmation_mode, # 安全确认模式(如自动确认/手动确认)
max_iterations,
max_budget_per_task=max_budget_per_task,
agent_to_llm_config=agent_to_llm_config,
agent_configs=agent_configs,
)
# 8. 发送初始事件(根据是否有初始消息设置Agent状态)
if not self._closed:
if initial_message:
# 有初始消息 → 向事件流添加用户消息,设置Agent状态为"运行中"
self.event_stream.add_event(initial_message, EventSource.USER)
self.event_stream.add_event(
ChangeAgentStateAction(AgentState.RUNNING),
EventSource.ENVIRONMENT,
)
else:
# 无初始消息 → 设置Agent状态为"等待用户输入"
self.event_stream.add_event(
ChangeAgentStateAction(AgentState.AWAITING_USER_INPUT),
EventSource.ENVIRONMENT,
)
finished = True # 标记执行完成
finally:
self._starting = False # 取消启动中标记
# 计算启动结果(是否成功:执行完成且运行时连接成功)
success = finished and runtime_connected
duration = time.time() - started_at # 计算启动耗时
# 日志元数据(用于监控和分析)
# 记录启动结果日志
2.4.2 初始化Agent
_start_agent_loop 会调用 initialize_agent 初始化 Agent。session初始化完成后调用initialize_agent进一步完成agent的其余模块初始化工作,首先创建llm和agent,然后调用agent_session.start()。
核心作用
initialize_agent 是 OpenHands 框架中 Agent实例的初始化核心方法,负责将用户设置、默认配置、第三方服务配置融合为最终运行配置,创建Agent实例并启动Agent会话,是连接配置与Agent运行的关键桥梁,确保Agent具备完成任务所需的所有能力(工具访问、LLM 支持、安全控制等)。
核心特色
- 配置融合机制:用户设置优先于默认配置,支持安全、沙盒、Git、第三方服务等多维度配置的灵活覆盖,兼顾通用性与个性化需求。
- 模块化压缩器设计:默认启用三阶段上下文压缩器流水线,按 "对话窗口→浏览器输出→LLM 总结" 顺序优化上下文,平衡上下文相关性与模型输入长度限制。
- 完整的服务配置:自动配置 MCP 服务器(Agent与工具的通信枢纽),支持自定义 MCP 配置扩展,适配不同部署环境的工具通信需求。
- 安全与隐私保护 :敏感信息(如沙盒 API 密钥)通过
get_secret_value()安全提取,未知错误仅返回错误类型,避免泄露敏感配置。 - 丰富的扩展参数:支持 Git 仓库访问、自定义密钥、会话指令等扩展参数,适配代码开发、第三方服务集成等复杂场景。
- 精细化错误处理:区分不同类型错误(微Agent验证错误、值错误、未知错误),返回针对性错误信息,便于问题排查。
- 状态可视化 :初始化开始时发送
AgentState.LOADING状态事件,让客户端实时感知Agent启动进度,提升用户体验。
初始化流程图
initialize_agent 代码如下:
python
async def initialize_agent(
self,
settings: Settings, # 用户/会话设置(含Agent类型、安全配置等)
initial_message: Optional[MessageAction] = None, # 初始用户消息(可选)
replay_json: Optional[str] = None, # 会话回放JSON(可选)
) -> None:
"""
初始化Agent核心流程:
1. 更新会话配置(融合用户设置与默认配置)
2. 配置MCP服务器(用于工具通信)
3. 初始化Agent配置(含上下文压缩器)
4. 创建Agent实例并启动Agent会话
"""
# 1. 发送Agent状态变更事件:标记为"加载中"
self.agent_session.event_stream.add_event(
AgentStateChangedObservation('', AgentState.LOADING),
EventSource.ENVIRONMENT, # 事件来源:环境
)
# 2. 融合用户设置与默认配置(用户设置优先)
# 确定Agent类型(用户设置优先,否则用默认)
agent_cls = settings.agent or self.config.default_agent
# 安全配置:确认模式(用户设置优先)
self.config.security.confirmation_mode = (
self.config.security.confirmation_mode
if settings.confirmation_mode is None
else settings.confirmation_mode
)
# 安全配置:安全分析器(用户设置优先)
self.config.security.security_analyzer = (
self.config.security.security_analyzer
if settings.security_analyzer is None
else settings.security_analyzer
)
# 沙盒配置:基础容器镜像(用户设置优先)
self.config.sandbox.base_container_image = (
settings.sandbox_base_container_image
or self.config.sandbox.base_container_image
)
# 沙盒配置:运行时容器镜像(用户设置优先,逻辑:基础镜像或运行时镜像有一个设置则用用户值)
self.config.sandbox.runtime_container_image = (
settings.sandbox_runtime_container_image
if settings.sandbox_base_container_image
or settings.sandbox_runtime_container_image
else self.config.sandbox.runtime_container_image
)
# 3. Git配置:若用户设置提供,覆盖默认值
git_user_name = getattr(settings, 'git_user_name', None)
if git_user_name is not None:
self.config.git_user_name = git_user_name
git_user_email = getattr(settings, 'git_user_email', None)
if git_user_email is not None:
self.config.git_user_email = git_user_email
# 4. 任务配置:最大迭代次数(用户设置优先)
max_iterations = settings.max_iterations or self.config.max_iterations
# 5. 任务配置:单任务最大预算(用户设置优先,支持None值)
max_budget_per_task = (
settings.max_budget_per_task
if settings.max_budget_per_task is not None
else self.config.max_budget_per_task
)
# 6. 第三方服务配置:搜索API密钥、沙盒API密钥
self.config.search_api_key = settings.search_api_key
if settings.sandbox_api_key:
# 提取沙盒API密钥的实际值(get_secret_value()用于安全存储的密钥)
self.config.sandbox.api_key = settings.sandbox_api_key.get_secret_value()
# 7. MCP服务器配置(用于Agent与工具的通信)
# 若用户设置提供自定义MCP配置,合并到全局配置
mcp_config = getattr(settings, 'mcp_config', None)
if mcp_config is not None:
self.config.mcp = self.config.mcp.merge(mcp_config)
# 默认添加OpenHands的MCP服务器(HTTP + STDIO类型)
openhands_mcp_server, openhands_mcp_stdio_servers = (
OpenHandsMCPConfigImpl.create_default_mcp_server_config(
self.config.mcp_host, self.config, self.user_id
)
)
if openhands_mcp_server:
self.config.mcp.shttp_servers.append(openhands_mcp_server)
self.config.mcp.stdio_servers.extend(openhands_mcp_stdio_servers)
# 8. Agent配置初始化
# 获取指定Agent类型的配置
agent_config = self.config.get_agent_config(agent_cls)
# 传递运行时信息到Agent配置(用于工具的运行时特定行为)
agent_config.runtime = self.config.runtime
# 获取Agent对应的LLM配置
agent_name = agent_cls if agent_cls is not None else 'agent'
llm_config = self.config.get_llm_config_from_agent(agent_name)
# 若启用默认上下文压缩器,配置压缩器流水线
if settings.enable_default_condenser:
"""
默认压缩器流水线包含三个阶段(顺序重要):
1. 对话窗口压缩器:处理显式的压缩请求
2. 浏览器输出压缩器:限制浏览器观察结果的大小(注意力窗口=2)
3. LLM总结压缩器:限制传递给LLM的上下文大小
顺序设计原因:先处理浏览器输出,可减少总结成本(仅保留最新浏览器输出)
"""
max_events_for_condenser = settings.condenser_max_size or 120 # 压缩器最大事件数(默认120)
default_condenser_config = CondenserPipelineConfig(
condensers=[
ConversationWindowCondenserConfig(),
BrowserOutputCondenserConfig(attention_window=2),
LLMSummarizingCondenserConfig(
llm_config=llm_config,
keep_first=4, # 保留前4个事件(不压缩)
max_size=max_events_for_condenser, # 压缩后最大事件数
),
]
)
agent_config.condenser = default_condenser_config
# 9. 创建Agent实例(通过Agent工厂方法获取对应类型的Agent类)
agent = Agent.get_cls(agent_cls)(agent_config, self.llm_registry)
# 10. 绑定LLM重试监听器(Agent会话中LLM重试时触发通知)
self.llm_registry.retry_listener = self._notify_on_llm_retry
# 11. 提取ConversationInitData类型设置中的扩展参数(若适用)
git_provider_tokens = None # Git提供商令牌(用于代码仓库访问)
selected_repository = None # 选中的代码仓库
selected_branch = None # 选中的仓库分支
custom_secrets = None # 自定义密钥(用于第三方服务访问)
conversation_instructions = None # 会话指令(自定义Agent行为)
if isinstance(settings, ConversationInitData):
git_provider_tokens = settings.git_provider_tokens
selected_repository = settings.selected_repository
selected_branch = settings.selected_branch
custom_secrets = settings.custom_secrets
conversation_instructions = settings.conversation_instructions
# 12. 启动Agent会话(核心步骤)
try:
await self.agent_session.start(
runtime_name=self.config.runtime, # 运行时名称(如沙盒类型)
config=self.config, # 完整配置
agent=agent, # Agent实例
max_iterations=max_iterations, # 最大迭代次数
max_budget_per_task=max_budget_per_task, # 单任务最大预算
agent_to_llm_config=self.config.get_agent_to_llm_config_map(), # Agent-LLM配置映射
agent_configs=self.config.get_agent_configs(), # 所有Agent配置
git_provider_tokens=git_provider_tokens, # Git令牌
custom_secrets=custom_secrets, # 自定义密钥
selected_repository=selected_repository, # 选中仓库
selected_branch=selected_branch, # 选中分支
initial_message=initial_message, # 初始用户消息
conversation_instructions=conversation_instructions, # 会话指令
replay_json=replay_json, # 会话回放数据
)
except MicroagentValidationError as e:
# 微Agent验证错误:输出详细错误信息(帮助用户排查配置问题)
return
except ValueError as e:
# 值错误:区分微Agent相关错误和普通值错误
self.logger.exception(f"创建Agent会话失败: {e}")
error_message = str(e)
return
except Exception as e:
# 其他未知错误:仅输出错误类型(避免泄露敏感信息)
return
2.4 用户交互(oh_user_action)逻辑
2.4.1 用户发消息
当用户发消息,就是通过socket在 oh_user_action 函数通过conversation_manager向事件中心中添加一条事件。代码位于 openhands\server\listen_socket.py。
python
@sio.event
async def oh_user_action(connection_id: str, data: dict[str, Any]) -> None:
await conversation_manager.send_to_event_stream(connection_id, data)
旧API 会调用到这里。
python
@sio.event
async def oh_action(connection_id: str, data: dict[str, Any]) -> None:
# TODO: Remove this handler once all clients are updated to use oh_user_action
# Keeping for backward compatibility with in-progress sessions
await conversation_manager.send_to_event_stream(connection_id, data)
2.4.2 事件添加
send_to_event_stream 位于 openhands\server\conversation_manager\standalone_conversation_manager.py。最终调用到 session.dispatch 向事件流发送事件。
python
async def send_to_event_stream(self, connection_id: str, data: dict):
# If there is a local session running, send to that
sid = self._local_connection_id_to_session_id.get(connection_id)
if not sid:
raise RuntimeError(f'no_connected_session:{connection_id}')
await self.send_event_to_conversation(sid, data)
async def send_event_to_conversation(self, sid: str, data: dict):
session = self._local_agent_loops_by_sid.get(sid)
if not session:
raise RuntimeError(f'no_conversation:{sid}')
await session.dispatch(data)
dispatch 代码如下。
python
async def dispatch(self, data: dict) -> None:
# ...
self.agent_session.event_stream.add_event(event, EventSource.USER)
2.4.3 事件处理
当一条事件被加入系统的事件流后,究竟会触发哪些模块的响应?要解答这个问题,我们首先需要梳理系统中已订阅事件流的核心模块 ------ 它们就像时刻待命的接收者,各自守在专属的消息通道旁:Session 模块订阅了 SERVER 通道,Runtime 模块对应 RUNTIME 通道,Memory 模块监听着 MEMORY 通道,而 AgentController 模块则聚焦于 AGENT_CONTROLLER 通道。
当用户发送一条消息并进入事件流后,这条消息会以广播的形式扩散到所有订阅通道,各个模块会通过预先注册的回调函数启动相应处理流程,具体流程如下:
AgentController 模块会在 _on_event 函数里面处理。
python
async def _on_event(self, event: Event) -> None:
if hasattr(event, 'hidden') and event.hidden:
return
self.state_tracker.add_history(event)
if isinstance(event, Action):
await self._handle_action(event)
elif isinstance(event, Observation):
await self._handle_observation(event)
should_step = self.should_step(event)
if should_step:
self.log(
'debug',
f'Stepping agent after event: {type(event).__name__}',
extra={'msg_type': 'STEPPING_AGENT'},
)
await self._step_with_exception_handling()
elif isinstance(event, MessageAction) and event.source == EventSource.USER:
# If we received a user message but aren't stepping, log why
self.log(
'warning',
f'Not stepping agent after user message. Current state: {self.get_agent_state()}',
extra={'msg_type': 'NOT_STEPPING_AFTER_USER_MESSAGE'},
)
_handle_action 会调用 _handle_message_action。
python
async def _handle_action(self, action: Action) -> None:
"""Handles an Action from the agent or delegate."""
if isinstance(action, ChangeAgentStateAction):
await self.set_agent_state_to(action.agent_state) # type: ignore
elif isinstance(action, MessageAction):
await self._handle_message_action(action)
elif isinstance(action, AgentDelegateAction):
await self.start_delegate(action)
assert self.delegate is not None
# Post a MessageAction with the task for the delegate
if 'task' in action.inputs:
self.event_stream.add_event(
MessageAction(content='TASK: ' + action.inputs['task']),
EventSource.USER,
)
await self.delegate.set_agent_state_to(AgentState.RUNNING)
return
elif isinstance(action, AgentFinishAction):
self.state.outputs = action.outputs
await self.set_agent_state_to(AgentState.FINISHED)
elif isinstance(action, AgentRejectAction):
self.state.outputs = action.outputs
await self.set_agent_state_to(AgentState.REJECTED)
_handle_message_action 中,AgentController 模块会生成一条 RecallAction 事件并再次推入事件流,该事件的类型会根据当前是否为用户首次输入来判定:若是首次输入则设为 RecallType.WORKSPACE_CONTEXT,反之则设为 RecallType.KNOWLEDGE;
python
async def _handle_message_action(self, action: MessageAction) -> None:
"""Handles message actions from the event stream.
Args:
action (MessageAction): The message action to handle.
"""
if action.source == EventSource.USER:
# Use info level if LOG_ALL_EVENTS is set
log_level = (
'info' if os.getenv('LOG_ALL_EVENTS') in ('true', '1') else 'debug'
)
self.log(
log_level,
str(action),
extra={'msg_type': 'ACTION', 'event_source': EventSource.USER},
)
# if this is the first user message for this agent, matters for the microagent info type
first_user_message = self._first_user_message()
is_first_user_message = (
action.id == first_user_message.id if first_user_message else False
)
recall_type = (
RecallType.WORKSPACE_CONTEXT
if is_first_user_message
else RecallType.KNOWLEDGE
)
recall_action = RecallAction(query=action.content, recall_type=recall_type)
self._pending_action = recall_action
# this is source=USER because the user message is the trigger for the microagent retrieval
self.event_stream.add_event(recall_action, EventSource.USER)
if self.get_agent_state() != AgentState.RUNNING:
await self.set_agent_state_to(AgentState.RUNNING)
elif action.source == EventSource.AGENT:
# If the agent is waiting for a response, set the appropriate state
if action.wait_for_response:
await self.set_agent_state_to(AgentState.AWAITING_USER_INPUT)
AgentController 模块会在 _on_event 函数里面调用 agent.step 方法,启动智能体的核心处理流程。
这里的 agent.step 的具体逻辑取决于配置文件中指定的智能体类型。以常见的 CodeActAgent 为例,该方法会指向 OpenHands/openhands/agenthub/codeact_agent/codeact_agent.py 中的 step 函数。最终将整理后的信息转化为符合大语言模型输入格式的 messages。
python
def step(self, state: State) -> 'Action':
"""Performs one step using the CodeAct Agent.
This includes gathering info on previous steps and prompting the model to make a command to execute.
Parameters:
- state (State): used to get updated info
Returns:
- CmdRunAction(command) - bash command to run
- IPythonRunCellAction(code) - IPython code to run
- AgentDelegateAction(agent, inputs) - delegate action for (sub)task
- MessageAction(content) - Message action to run (e.g. ask for clarification)
- AgentFinishAction() - end the interaction
- CondensationAction(...) - condense conversation history by forgetting specified events and optionally providing a summary
- FileReadAction(path, ...) - read file content from specified path
- FileEditAction(path, ...) - edit file using LLM-based (deprecated) or ACI-based editing
- AgentThinkAction(thought) - log agent's thought/reasoning process
- CondensationRequestAction() - request condensation of conversation history
- BrowseInteractiveAction(browser_actions) - interact with browser using specified actions
- MCPAction(name, arguments) - interact with MCP server tools
"""
# Continue with pending actions if any
if self.pending_actions:
return self.pending_actions.popleft()
# if we're done, go back
latest_user_message = state.get_last_user_message()
if latest_user_message and latest_user_message.content.strip() == '/exit':
return AgentFinishAction()
# Condense the events from the state. If we get a view we'll pass those
# to the conversation manager for processing, but if we get a condensation
# event we'll just return that instead of an action. The controller will
# immediately ask the agent to step again with the new view.
condensed_history: list[Event] = []
match self.condenser.condensed_history(state):
case View(events=events):
condensed_history = events
case Condensation(action=condensation_action):
return condensation_action
initial_user_message = self._get_initial_user_message(state.history)
messages = self._get_messages(condensed_history, initial_user_message)
params: dict = {
'messages': messages,
}
params['tools'] = check_tools(self.tools, self.llm.config)
params['extra_body'] = {
'metadata': state.to_llm_metadata(
model_name=self.llm.config.model, agent_name=self.name
)
}
response = self.llm.completion(**params)
logger.debug(f'Response from LLM: {response}')
actions = self.response_to_actions(response)
logger.debug(f'Actions after response_to_actions: {actions}')
for action in actions:
self.pending_actions.append(action)
return self.pending_actions.popleft()
当大语言模型处理完成并返回结果后,该结果会被封装成 Action 对象重新送入事件流。假设模型直接返回了 MessageAction 类型的结果,Session 模块便会捕获这条事件,并将其中的内容转发至前端界面,完成向用户的反馈展示。
0xFF 参考
https://docs.all-hands.dev/openhands/usage/architecture/backend
当AI Agent从"玩具"走向"工具",我们该关注什么?Openhands架构解析【第二篇:Agent 相关核心概念】 克里
当AI Agent从"玩具"走向"工具",我们该关注什么?Openhands架构解析【第一篇:系列导读】 克里