集成新的Beaver后端服务到应用实例中,替换原有的nanobot实现。 主要变更包括: - 在Dockerfile和环境配置中添加Beaver相关路径和配置变量 - 更新工作目录结构从.nanobot到.beaver - 实现Beaver引擎加载器,支持配置文件加载和工具组装 - 添加内置工具如ListDirectoryTool、ReadFileTool、SearchFilesTool - 更新消息处理流程,支持通道适配器和网关模式 - 重构技能系统,支持显式工具提示和嵌入式检索 - 改进错误处理和生命周期管理 此变更使应用实例能够使用统一的Beaver后端进行AI代理运行时管理。
269 lines
8.8 KiB
Python
269 lines
8.8 KiB
Python
"""Application service for agent entry.
|
||
|
||
这层的职责是把“接口层如何调用 AgentLoop”统一收口。
|
||
|
||
接口层以后不应该各自做这些事情:
|
||
1. 自己 new `AgentLoop`
|
||
2. 自己决定何时 `boot()`
|
||
3. 自己处理 direct run 的同步/异步包装
|
||
|
||
统一放在 `AgentService` 后,CLI / Web / Gateway 才能共享同一条运行主链。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import asyncio
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from beaver.engine import AgentLoop, AgentProfile, AgentRunResult, EngineLoader
|
||
from beaver.foundation.events import InboundMessage, OutboundMessage
|
||
|
||
|
||
class AgentService:
|
||
"""面向 interfaces 的统一 agent 运行入口。
|
||
|
||
这里明确区分两种调用模式:
|
||
1. direct mode
|
||
- 不启动后台运行循环
|
||
- 直接调用 `process_direct()` / `run_direct()`
|
||
2. running mode
|
||
- 先 `await start()`
|
||
- 之后所有外部任务都必须走 `submit_direct()`
|
||
- 不允许再直接调用 `process_direct()`
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
*,
|
||
workspace: str | Path | None = None,
|
||
config_path: str | Path | None = None,
|
||
profile: AgentProfile | None = None,
|
||
loader: EngineLoader | None = None,
|
||
) -> None:
|
||
self.profile = profile or AgentProfile()
|
||
self.loader = loader or EngineLoader(workspace=workspace, config_path=config_path)
|
||
self._loop: AgentLoop | None = None
|
||
self._run_task: asyncio.Task[None] | None = None
|
||
|
||
def create_loop(self) -> AgentLoop:
|
||
"""创建并缓存当前 service 使用的 AgentLoop。"""
|
||
|
||
if self._loop is None:
|
||
self._loop = AgentLoop(profile=self.profile, loader=self.loader)
|
||
self._loop.boot()
|
||
return self._loop
|
||
|
||
@property
|
||
def has_loop(self) -> bool:
|
||
"""当前 service 是否已经创建过 loop。"""
|
||
|
||
return self._loop is not None
|
||
|
||
@property
|
||
def is_running(self) -> bool:
|
||
"""当前 service 是否处于 running mode。"""
|
||
|
||
return self._run_task is not None and not self._run_task.done()
|
||
|
||
def close(self) -> None:
|
||
"""关闭当前 service 持有的 runtime。"""
|
||
|
||
if self._run_task is not None and not self._run_task.done():
|
||
raise RuntimeError("AgentService.close() requires stop() before closing a running loop")
|
||
self._run_task = None
|
||
if self._loop is None:
|
||
return
|
||
try:
|
||
self._loop.close()
|
||
finally:
|
||
self._loop = None
|
||
|
||
async def start(self) -> None:
|
||
"""启动后台运行循环,进入 running mode。
|
||
|
||
进入 running mode 后:
|
||
- 外部任务必须通过 `submit_direct()` 提交
|
||
- `process_direct()` 不再允许直接调用
|
||
"""
|
||
|
||
if self._run_task is not None and not self._run_task.done():
|
||
return
|
||
loop = self.create_loop()
|
||
self._run_task = asyncio.create_task(loop.run())
|
||
while not loop.is_running:
|
||
if self._run_task.done():
|
||
await self._run_task
|
||
break
|
||
await asyncio.sleep(0)
|
||
|
||
async def _stop_impl(
|
||
self,
|
||
*,
|
||
timeout_seconds: float | None = None,
|
||
force: bool = False,
|
||
) -> None:
|
||
"""内部停止实现,支持 graceful timeout 和可选 force cancel。"""
|
||
|
||
if self._run_task is None:
|
||
return
|
||
run_task = self._run_task
|
||
loop = self.create_loop()
|
||
try:
|
||
await loop.stop()
|
||
if timeout_seconds is None:
|
||
await run_task
|
||
else:
|
||
try:
|
||
await asyncio.wait_for(asyncio.shield(run_task), timeout=timeout_seconds)
|
||
except asyncio.TimeoutError as exc:
|
||
if force:
|
||
run_task.cancel()
|
||
try:
|
||
await run_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
else:
|
||
raise TimeoutError(
|
||
f"AgentService.stop() timed out after {timeout_seconds} seconds while draining queued tasks"
|
||
) from exc
|
||
finally:
|
||
if run_task.done():
|
||
self._run_task = None
|
||
|
||
async def stop(
|
||
self,
|
||
*,
|
||
timeout_seconds: float | None = None,
|
||
force: bool = False,
|
||
) -> None:
|
||
"""停止后台运行循环并等待退出。
|
||
|
||
参数:
|
||
- `timeout_seconds`: graceful drain 的最长等待时间;`None` 表示一直等
|
||
- `force`: 超时后是否 cancel 掉运行循环 task
|
||
"""
|
||
|
||
await self._stop_impl(timeout_seconds=timeout_seconds, force=force)
|
||
|
||
async def shutdown(
|
||
self,
|
||
*,
|
||
timeout_seconds: float | None = None,
|
||
force: bool = False,
|
||
) -> None:
|
||
"""先停运行循环,再释放 runtime。"""
|
||
|
||
await self._stop_impl(timeout_seconds=timeout_seconds, force=force)
|
||
self.close()
|
||
|
||
async def process_direct(
|
||
self,
|
||
message: str,
|
||
**kwargs: Any,
|
||
) -> AgentRunResult:
|
||
"""异步 direct run 入口。
|
||
|
||
仅在 direct mode 下可用。
|
||
|
||
如果 service 已经 `start()` 进入 running mode,
|
||
调用方必须改用 `submit_direct()`,不能绕过运行队列直接执行。
|
||
"""
|
||
|
||
if self._run_task is not None and not self._run_task.done():
|
||
raise RuntimeError(
|
||
"AgentService.process_direct() is unavailable while the service is running; "
|
||
"use 'await AgentService.submit_direct(...)' after start()."
|
||
)
|
||
loop = self.create_loop()
|
||
return await loop.process_direct(message, **kwargs)
|
||
|
||
async def submit_direct(
|
||
self,
|
||
message: str,
|
||
**kwargs: Any,
|
||
) -> AgentRunResult:
|
||
"""向 running mode 下的 loop 提交 direct task。
|
||
|
||
这是 `start()` 之后唯一合法的外部任务入口。
|
||
"""
|
||
|
||
loop = self.create_loop()
|
||
return await loop.submit_direct(message, **kwargs)
|
||
|
||
async def handle_inbound_message(self, inbound: InboundMessage) -> OutboundMessage:
|
||
"""把 bus inbound 映射成标准 runtime 调用,并返回结构化 outbound。"""
|
||
|
||
try:
|
||
result = await self.submit_direct(
|
||
inbound.content,
|
||
session_id=inbound.session_id,
|
||
source=f"gateway:{inbound.channel}",
|
||
user_id=inbound.user_id,
|
||
title=inbound.title,
|
||
execution_context=inbound.execution_context,
|
||
model=inbound.model,
|
||
provider_name=inbound.provider_name,
|
||
embedding_model=inbound.embedding_model,
|
||
)
|
||
except Exception as exc:
|
||
return self.build_outbound_error(inbound, detail=str(exc))
|
||
return self.build_outbound_message(inbound, result)
|
||
|
||
@staticmethod
|
||
def build_outbound_message(inbound: InboundMessage, result: AgentRunResult) -> OutboundMessage:
|
||
"""把一次 runtime 正常结果转成 bus outbound。"""
|
||
|
||
return OutboundMessage(
|
||
message_id=inbound.message_id,
|
||
channel=inbound.channel,
|
||
session_id=result.session_id,
|
||
run_id=result.run_id,
|
||
content=result.output_text,
|
||
finish_reason=result.finish_reason,
|
||
provider_name=result.provider_name,
|
||
model=result.model,
|
||
usage=dict(result.usage),
|
||
metadata={"inbound_metadata": dict(inbound.metadata)},
|
||
)
|
||
|
||
@staticmethod
|
||
def build_outbound_error(
|
||
inbound: InboundMessage,
|
||
*,
|
||
detail: str,
|
||
finish_reason: str = "error",
|
||
) -> OutboundMessage:
|
||
"""把 inbound 处理失败转换成结构化 outbound 错误消息。"""
|
||
|
||
return OutboundMessage(
|
||
message_id=inbound.message_id,
|
||
channel=inbound.channel,
|
||
session_id=inbound.session_id,
|
||
content=detail,
|
||
finish_reason=finish_reason,
|
||
metadata={"error": detail, "inbound_metadata": dict(inbound.metadata)},
|
||
)
|
||
|
||
def run_direct(
|
||
self,
|
||
message: str,
|
||
**kwargs: Any,
|
||
) -> AgentRunResult:
|
||
"""同步 direct run 包装。
|
||
|
||
主要给当前 CLI 或简单脚本使用。真正的长期方向仍然是让 interfaces
|
||
在 direct mode 下直接走 `await process_direct(...)`。
|
||
"""
|
||
|
||
try:
|
||
asyncio.get_running_loop()
|
||
except RuntimeError:
|
||
pass
|
||
else:
|
||
raise RuntimeError(
|
||
"AgentService.run_direct() cannot be used inside an active event loop; "
|
||
"use 'await AgentService.process_direct(...)' instead."
|
||
)
|
||
return asyncio.run(self.process_direct(message, **kwargs))
|