OpenManus是一个先进的轻量级、基于 ReAct (思考-行动) 模式的AI智能体(Agent)框架。它通过一个 FastAPI 后端提供服务,并利用 Server-Sent Events (SSE) 技术,为前端的聊天界面提供实时的流式响应。

技术改造汇报:实现 Agent 与前端的异步交互 (追问)

汇报人: 学会歌唱的老周
日期: 2025/6/24
主题: 将 openmanus中的/app/tool下的ask_human 工具从同步控制台输入升级为与 Web 前端的异步回调交互


一、 项目背景与目标

1. 原始问题:
我们现有的 AI Agent 框架中,ask_human 工具被设计用于在 Agent 遇到不确定信息时向人类寻求帮助。然而,其原始实现依赖于后端的同步 input() 函数,这会导致以下严重问题:

  • 阻塞服务: input() 会完全阻塞执行线程,导致整个 Web 服务在等待用户于控制台输入时停止响应,无法处理任何其他并发请求。
  • 糟糕的用户体验: 用户无法在熟悉的 Web 聊天界面进行交互,必须切换到后端服务器的控制台,这在生产环境中是不可行的。

2. 改造目标:
本次技术改造的核心目标是:移除对 input() 的依赖,建立一个非阻塞的、通过 Web 前端进行异步交互的回调机制。

  • 后端: Agent 在需要时能够“暂停”任务,向前端发送提问请求。
  • 前端: 聊天界面能接收到提问请求,解锁输入框让用户回答。
  • 通信: 用户提交回答后,后端能接收到答案并从“暂停”处无缝“恢复”执行。

二、 整体技术方案

我们采用了一种基于 “异常控制流”“状态管理” 的异步设计方案,以最小化对核心 Agent 框架的侵入。

核心流程图:

用户 前端 (浏览器) 后端 (SSE 流) Agent (执行逻辑) 后端 (API 端点) 1. 输入问题 (e.g., "我要回家") 2. 发起 /api/chat/stream 请求 3. 创建 Agent 并调用 run_stream() 4. think(): 决定需要向人类提问 5. act(): 调用 ask_human 工具 6. ask_human: 准备 Future 并 **抛出 HumanInputRequiredError 异常** 此时,ask_human 的执行中断 7. run_stream() **捕获异常** 知道需要暂停,开始等待 Future 8. 发送 `human_input_request` 事件 (包含提问内容) 9. 显示提问 (e.g., "请问你家在哪?") 并解锁输入框 10. 输入答案 (e.g., "在XX小区") 11. 发起 POST /api/human_response 请求 (包含答案) 12. 查找等待中的 Agent, **解析 Future 并填入答案** 等待中的 Future 完成, run_stream() 从 await 处恢复 13. 将人类答案记入内存 14. 继续执行下一步 think()/act() (现在它知道家的位置了) 15. 继续 yield 后续结果 16. 将新结果流式传输给前端 17. 显示最终结果 (e.g., 路线规划) 用户 前端 (浏览器) 后端 (SSE 流) Agent (执行逻辑) 后端 (API 端点)
  1. 前端 (UI): 用户发起对话。
  2. 后端 (Agent): ask_human 工具被调用时,不再等待,而是抛出一个自定义的 HumanInputRequiredError 异常。这个异常本身不包含数据,仅作为信号。同时,它在一个挂载于 Agent 实例上的 Future 对象上设置了“等待点”。
  3. 后端 (Agent 循环): Agent 的主执行循环 (run_stream 方法) 捕获这个特定异常。它知道这是需要用户输入的信号,于是安全地 await 那个 Future 对象,将执行权交还给事件循环,从而不会阻塞服务
  4. 后端 (SSE): 事件循环得以继续,它检测到 Agent 上有“需要人类输入”的标志,立即通过 Server-Sent Events (SSE) 向前端发送一个 human_input_request 事件,其中包含提问内容。
  5. 前端 (UI): 接收到该事件,显示提问信息,并解锁输入框。
  6. 前端 (UI) -> 后端 (API): 用户输入答案后,前端向一个新的 API 端点 (/api/human_response) 发送 POST 请求,其中包含答案。
  7. 后端 (API): 该 API 端点找到对应的 Agent 实例,并将答案设置到之前创建的 Future 对象上,使其完成。
  8. 后端 (Agent 循环): awaitFuturerun_stream 方法立即获得结果,Agent 从暂停处恢复执行,整个流程无缝衔接。

三、 具体代码修改步骤
第1步:定义纯信号异常,打破循环导入

为了彻底解决模块间的循环依赖问题,我们创建了一个无任何内部依赖的异常类。

  • 文件: app/exceptions.py
# --- START OF FILE app/exceptions.py ---

class HumanInputRequiredError(Exception):
    """
    一个纯粹的信号异常,用于在需要人类输入时中断控制流。
    它不携带任何状态,所有状态都由 Agent 实例自身管理。
    """
    def __init__(self):
        super().__init__("Execution paused, waiting for human input.")

说明: 这是整个方案的基石,确保了异常可以在项目的任何地方被安全地引用而不会引发导入错误。

第2步:改造 ask_human 工具,使其抛出异常

我们将 ask_humanexecute 方法从同步阻塞改为异步非阻塞,通过抛出异常来传递信号。

  • 文件: app/tool/ask_human.py
# --- START OF FILE ask_human.py ---

import asyncio
from typing import TYPE_CHECKING

# 依赖注入后,无需在此 import HumanInputRequiredError
# from app.exceptions import HumanInputRequiredError
from app.tool import BaseTool

if TYPE_CHECKING:
    from app.agent.base import BaseAgent


class AskHuman(BaseTool):
    # ... name, description, parameters 不变 ...

    async def execute(self, inquire: str, agent: "BaseAgent", **kwargs) -> str:
        """
        通过抛出异常来暂停执行,并请求人类输入。
        """
        loop = asyncio.get_running_loop()

        # 1. 在 Agent 实例上存储状态和“等待点”(Future)
        agent._human_input_future = loop.create_future()
        agent._human_input_request = {"question": inquire}

        # 2. 抛出信号异常,将控制权交还给上层调用栈
        raise HumanInputRequiredError()

说明: ask_human 的职责从“等待并返回答案”转变为“设置状态并发出暂停信号”。

第3步:修改 Agent 基类,增加状态字段并处理异常

我们在 BaseAgent 中添加了用于存储状态的私有属性,并修改了主执行循环 run_stream 来捕获和处理我们的自定义异常。

  • 文件: app/agent/base.py
# --- START OF FILE base.py ---

# ... imports ...
from pydantic import PrivateAttr
# from app.exceptions import HumanInputRequiredError # 同样由注入解决

class BaseAgent(BaseModel, ABC):
    # ... 其他字段 ...

    # 新增:用于人机交互的私有实例属性
    _human_input_request: Optional[dict] = PrivateAttr(default=None)
    _human_input_future: Optional["asyncio.Future"] = PrivateAttr(default=None)

    # ... 其他方法 ...

    async def run_stream(self, request: Optional[str] = None) -> AsyncGenerator[str, None]:
        # ... (循环开始)
            try:
                step_result = await self.step()
            except HumanInputRequiredError:
                # 1. 捕获到信号,此时 agent 实例上已有 future 和 request
                logger.info("run_stream caught HumanInputRequiredError...")
                yield ""  # 确保事件循环继续,以便向前端发送事件

                # 2. 安全地等待 future,不会阻塞服务
                future = self._human_input_future
                if future:
                    try:
                        human_response = await asyncio.wait_for(future, timeout=300)
                        # 3. 收到答案后,将其记入内存并作为本步骤结果
                        tool_msg = Message.tool_message(
                            content=human_response,
                            tool_call_id=f"human-input-{self.current_step}",
                            name="ask_human",
                        )
                        self.memory.add_message(tool_msg)
                        step_result = f"Received human input: {human_response}"
                    except asyncio.TimeoutError:
                        step_result = "Timed out waiting for human input."
            # ... (循环继续)

说明: 这是后端逻辑的核心。run_stream 成为了异常的最终处理器,它将一个深层调用栈中的暂停请求,转换成了顶层的异步等待。

第4步:在 toolcall 模块中允许异常穿透

为确保异常能从工具执行层传递到 Agent 基类的处理层,我们在 execute_tool 中添加了特定的 except 块。

  • 文件: app/agent/toolcall.py
# --- START OF FILE toolcall.py ---

# ... imports ...
# from app.exceptions import HumanInputRequiredError # 同样由注入解决

class ToolCallAgent(ReActAgent):
    # ... 其他方法 ...
    async def execute_tool(self, command: ToolCall) -> str:
        # ...
        try:
            # ...
            result = await self.available_tools.execute(name=name, tool_input=args)
            # ...
            return observation
        except json.JSONDecodeError:
            # ...
        except HumanInputRequiredError:
            raise  # 明确地重新抛出,让它继续向上传播
        except Exception as e:
            # ...

说明: 这一步确保了我们的信号异常不会被通用的 except Exception 错误地捕获和处理。

第5步:升级 Web 服务,实现前端后端完整闭环

我们对 server_web.py 进行了全面升级,包括:

  1. 依赖注入:在程序入口处解决顽固的 NameError
  2. 状态管理:使用全局字典 WAITING_AGENTS 存储等待响应的 Agent 实例。
  3. 前端 JavaScript:添加了对 human_input_request 事件的监听和处理逻辑。
  4. 新 API 端点:创建了 /api/human_response 用于接收前端提交的答案。
  • 文件: server_web.py
# --- START OF FILE server_web.py ---

# ... 集中导入所有模块和异常 ...
from app.agent import base as agent_base_module
from app.agent import toolcall as toolcall_module
# ...
from app.exceptions import HumanInputRequiredError

# 手动注入异常类,解决加载顺序问题
agent_base_module.HumanInputRequiredError = HumanInputRequiredError
toolcall_module.HumanInputRequiredError = HumanInputRequiredError
# ...

# 全局状态,用于会话保持
WAITING_AGENTS: dict[str, agent_base_module.BaseAgent] = {}

# --- 在 event_generator 中 ---
async def event_generator():
    # ...
    async for chunk in agent.run_stream(prompt):
        # 检查 agent 是否有待处理的请求
        if agent._human_input_request:
            # 发送特殊事件到前端
            request_data = agent._human_input_request
            request_data["session_id"] = session_id
            yield f"event: human_input_request\\ndata: {json.dumps(request_data)}\\n\\n"
            agent._human_input_request = None # 清除标志,避免重复发送
        # ...

# --- 新增 API 端点 ---
@app.post("/api/human_response")
async def human_response(request: HumanResponseRequest):
    agent = WAITING_AGENTS.get(request.session_id)
    if agent and agent._human_input_future:
        # 解析对应的 Future,让等待中的 run_stream 恢复执行
        agent._human_input_future.set_result(request.response)
    # ...

# --- 前端 JavaScript 修改 ---
// eventSource.addEventListener('human_input_request', function(event) {
//     const data = JSON.parse(event.data);
//     currentSessionId = data.session_id;
//     waitingForHumanInput = true;
//
//     // 显示提问消息
//     addMessage(`**我需要你的帮助**: ${data.question}`, 'human-inquire');
//
//     // 解锁输入框
//     setInputState(true, "请输入您的回答...");
// });
//
// function sendMessage() {
//     // ...
//     if (waitingForHumanInput) {
//         sendHumanResponse(message);
//     } else {
//         startNewChatStream(message);
//     }
// }

说明: server_web.py 成为了整个异步交互流程的“调度中心”,它连接了后端的暂停信号和前端的用户界面。


四、 成果与总结

通过本次技术改造,我们成功实现了:

  • 完全非阻塞的后端服务,提升了系统的并发处理能力和稳定性。
  • 无缝的交互体验,用户可以在统一的 Web 聊天界面中完成所有交互,无需切换上下文。
  • 高内聚、低耦合的架构,对 Agent 核心逻辑的修改降至最低,保证了框架的通用性和可维护性。

本次升级为 Agent 在未来集成更多需要人类参与的复杂任务(如人机协作、动态纠错等)奠定了坚实的技术基础。

openmanus项目主要代码文件详解

项目的核心是一个清晰的Agent类继承体系:
BaseAgent -> ReActAgent -> ToolCallAgent -> Manus / Validator
这种分层结构实现了优秀的“关注点分离”:
1 base.py (BaseAgent): 作为所有Agent的基石,负责管理最基本的状态(如:空闲、运行中、已完成、错误)和记忆(对话历史)。

2 react.py (ReActAgent): 实现了ReAct模式的核心逻辑,将BaseAgent中抽象的step(步骤)分解为think(思考)和act(行动)两个更具体的阶段。它还负责将每一步的进展(思考内容、工具调用、工具结果)打包成结构化的HTML,实时推送到前端。

3 toolcall.py (ToolCallAgent): 这是所有需要与“工具”交互的Agent的“主力军”。它具体实现了think和act:
think(): 调用大语言模型(LLM),将当前的对话历史和可用的工具列表发送给模型,让模型进行“思考”并决定下一步要调用哪些工具。
act(): 接收think()阶段生成的工具调用指令,并逐一执行它们,然后将执行结果反馈给模型。

4 manus.py & validator.py (Manus & Validator): 这是两个具体的“应用级”Agent。它们都是ToolCallAgent的实例,但通过配置不同的**提示词(Prompt)和工具集(Tool Collection)**来执行截然不同的任务。Manus专注于地理导航,而Validator则专注于评估Manus的回答质量。

5 llm.py文件的关键作用
llm.py 文件是整个系统与大语言模型(LLM)交互的唯一桥梁,它的职责非常关键和集中:

  • 统一的API接口 (LLM 类):
    它封装了与不同LLM API(如 OpenAI、Azure OpenAI)的通信细节。其他代码(如 ToolCallAgent)只需要和这个 LLM 类打交道,而无需关心底层API的具体实现。
  • 通过 new 方法实现了单例模式,确保对同一个配置(如 “default”, “manus”)的LLM实例在整个应用中是唯一的,有助于状态管理和资源节约。
  • 消息格式化 (format_messages 方法):
    LLM API对输入的 messages 列表有严格的格式要求。此方法负责将项目中内部的 Message 对象或字典,转换成标准化的OpenAI消息格式。
  • 能智能处理图片。如果模型支持图片输入(如 GPT-4o),它会将 base64_image 字段转换成API要求的 image_url 格式。如果模型不支持,它会优雅地忽略图片,只保留文本内容。
  • Token(令牌)计算与管理:
    TokenCounter 类: 这是一个非常精细的Token计算器。它不仅能计算文本的Token,还能根据OpenAI的官方规则,基于图片的尺寸和清晰度(low 或 high detail)精确计算图片的Token成本。
    count_message_tokens 方法: 能够精确计算整个对话历史的总Token数,这对于防止超出模型上下文窗口长度至关重要。
    check_token_limit & update_token_count: 在每次调用API前,LLM类都会使用这些方法检查Token是否会超限,并在调用后更新已使用的Token数量,为管理长对话提供了坚实的基础。
  • 核心API调用方法:
    ask(): 用于简单的文本问答。
    ask_with_images(): 专用于多模态模型,处理图文混合的输入。
    ask_tool(): 这是本项目的核心调用方法。它负责处理需要“函数调用/工具使用”的场景。它会将工具的定义(tools)和调用策略(tool_choice)一起发送给LLM。
  • 健壮性与容错:
    自动重试: 所有API调用方法都使用了 tenacity 库进行装饰,当发生网络错误或API临时不可用时,会自动以指数退避策略进行重试,大大增强了系统的稳定性。
    TokenLimitExceeded 异常: 当Token超限时,会抛出一个特定的异常,该异常不会被重试,而是直接向上层传递,由Agent逻辑进行处理(通常是终止任务)。
  • 工具调用格式修正: ask_tool 方法中包含了一段非常重要的逻辑。如果LLM没有在标准的tool_calls字段中返回工具调用,而是将其以 json … 的格式内嵌在 content 文本中,该方法能自动提取这部分JSON,将其解析并修正为标准的 tool_calls 格式。这极大地提高了对不同模型行为的兼容性和鲁棒性。

Validator是我们用来测评模型输出内容质量好坏。Validator - agent代码如下

class Validator(ToolCallAgent):
    """
    A versatile agent that evaluates response quality using multiple tools.

    This agent extends ToolCallAgent with a set of tools specifically designed for
    assessing the quality of responses.
    """

    name: str = "Validator"
    description: str = (
        "A versatile agent that evaluates response quality using multiple tools"
    )

    system_prompt: str = SYSTEM_PROMPT.format(directory=config.workspace_root)
    next_step_prompt: str = NEXT_STEP_PROMPT

    max_observe: int = 10000
    max_steps: int = 20

    # Add general-purpose tools to the tool collection
    # BrowserUseTool(),
    available_tools: ToolCollection = Field(
        default_factory=lambda: ToolCollection(
        TrainSearch(), HotelSearch(), FlightSearch(), LocationSearch(), WebSearch(), RoutePlanner()
        )
    )

    async def format_prompt(self, messages):
        tool_outputs = []

        final_prompt = "【用户的问题】: " + messages[1].get('content', '') + "\n\n"

        final_message = messages[-1].get('content', '')

        if isinstance(final_message, str) and '</think>' in final_message:
            final_message = final_message.split('</think>', 1)[-1].strip()
            final_prompt += "【待判别的回复】: " + final_message + "\n\n"
        else:
            final_prompt += "【待判别的回复】: " + final_message + "\n\n"

        for item in messages:
            if item.get('role') == 'tool':
                tool_name = item.get('name', 'unknown_tool')
                content = item.get('content', '')

                # 提取实际内容(去掉前面的描述性文字)
                if isinstance(content, str) and 'Observed output of cmd' in content:
                    content = content.split(':', 1)[-1].strip()

                # 尝试解析JSON内容(如果内容是JSON字符串)
                try:
                    import json
                    parsed_content = json.loads(content)
                    formatted_content = json.dumps(parsed_content, indent=2, ensure_ascii=False)
                except (json.JSONDecodeError, TypeError):
                    formatted_content = str(content)

                # 构建格式化输出
                tool_output = f"""
                【{tool_name}】输出结果:
                {formatted_content}
                {'-'*40}
                """
                tool_outputs.append(tool_output)

        # 将所有工具输出合并成一个字符串
        final_prompt += "【参考信息】,其中【】中为信息来源名称:\n"
        final_prompt += "\n".join(tool_outputs)
        return final_prompt

    async def think(self) -> bool:
        """Process current state and decide next actions with appropriate context."""
        # Store original prompt
        original_prompt = self.next_step_prompt
        #print("next_step_prompt:", self.next_step_prompt)

        result = await super().think()

        # Restore original prompt
        self.next_step_prompt = original_prompt

        return result

Validator prompt如下

from app.tool.current_location import get_current_location
import asyncio

SYSTEM_PROMPT = (
    "你是一个回复的评判员, 用于评估【待判别的回复】是否符合评估原则。"
)

current = asyncio.run(get_current_location())
if current:
    SYSTEM_PROMPT += '\n' + current

from datetime import datetime
# 获取当前时间
now = datetime.now()

# 格式化为 YYYY-MM-DD HH:MM:SS
formatted_time = now.strftime("%Y-%m-%d %H:%M:%S")

NEXT_STEP_PROMPT = f"""你将被提供以下信息:
1.【用户的问题】:包含用户的需求,和一些旅行约束。
2.【待判别的回复】:AI差旅助手的最终的回复。
3.【参考信息】:AI差旅助手推理过程中参考的信息,其中【】中为信息来源名称。
请依据以下定义的[评估原则]与[评估流程]判断回复的质量。


[评估原则]
1.【答案是否完整】:完整的答案出现在<answer>与</answer>之间,应为完整的行程计划。
    如果出现</tool_call>,</tool_response_thinking>等标签,或者函数调用,或者口语化的thinking过程。
    如果因为信息不足导致无法给出完整的行程规划并通过询问或者解释告知用户,也视作完整。
    如果没有【待判别的回复的工具调用结果】,没有工具调用,视为不完整。
    如果没有给出结论,视为不完整。
2.【逻辑是否合理】:待判别的回复的方案在时间线上逻辑合理,市内接驳/中转/转场,最大程度的满足用户的出行需求与约束,考虑到出行舒适度,符合旅行计划的常识。如果客观限制无法满足说明情况也算满足需求。
3.【幻觉是否存在】:【待判别的回复】中是否存在幻觉,所有的信息必须存在于【参考信息】中,并且保持一致。如果不一致,说明存在幻觉。
    所有信息,必须与【WebSearch】提供的信息保持一致。
    对于两个位置之间的路线、交通、距离、时间和导航信息,需要与【RoutePlanner】提供的信息保持一致。
    对于位置详情,需要与【LocationSearch】提供的信息保持一致。
    对于用户当前位置,需要与【CurrentLocation】提供的信息保持一致。
    对于特定点周围的位置,需要与【LocationAroundSearch】提供的信息保持一致。
    对于特定路线沿途的位置,需要与【RouteAlongSearch】提供的信息保持一致。
4.【信息是否真实】:重新调用工具验证【待判别的回复】中所有的涉及的时间、距离、地点、火车、飞机、酒店等相关信息(如出发/到达时间、预计时间、距离描述、航班号、车次、酒店位置等精准描述)。
    禁止抽样验证,所有信息必须经过验证,带有【可能】的信息视为错误信息。
    严格按照要求重新调用工具检验真实性,【参考信息】仅供参考不作为真实信息。
5.【约束是否满足】:请检查用户是否有特定约束,如交通工具类型,避开的地点航司,费用等,如果有的话需求是否满足。如果客观限制无法满足说明情况也算满足需求。
6.【需求是否满足】:请检查用户是否有特定需求,如停留时间,要经历的景点,需要完成的活动(美食, 探店等),如果有的话需求是否满足。如果客观限制无法满足说明情况也算满足需求。
7.【突发情况是否有备选方案】:请你首先判断是否有突发情况的可能性,例如恶劣天气/时间限制/机票酒店住宿资源紧张等,如果有突发情况发生的可能的话,是否提供了备选方案。

[注意事项]
1.当方案中包含多个行程(如多趟航班、列车),请务必逐一核对每个行程的关键信息(如item_key、出发/到达时间、航班号/列车编号、出发/到达城市)。
不得将不同行程的信息混淆使用,请保持每条行程信息的独立性和一致性,防止逻辑错误和误判。
2.请你通过工具,完整核实【待判别的回复】中需要验证的信息的真实性,特别是机票,火车和酒店的关键信息,如航班号、列车车次、出发/到达时间、酒店地址名称等等。

[Tools与调用方式]
一.可用到的tools:
1.联网搜索是一个函数调用:
WebSearch(query)
query:搜索的关键词,要做到言简意赅。
使用时遵循以下格式<tool_call>web_search(**kwargs)</tool_call>,如<tool_call>web_search(query="2025年5月1日 北京 天气")</tool_call>
注意机票/酒店的相关查询,使用专门的机票/酒店工具,不要使用web_search。
注意不要重复搜索已经搜索过的关键词。

2.你具有poi搜索的能力,可以根据地点描述查询地址。
poi搜索是一个函数调用:
LocationSearch(query)
query:地点的描述
使用时遵循以下格式<tool_call>poi_search(**kwargs)</tool_call>,如<tool_call>poi_search(query="北邮")</tool_call>
注意poi_search结果可能会有多个,如果不确定是哪一个,可以把列表告诉用户,让用户从列表中选择。

3.你具有路线规划查询的能力,可以获得两个地点之间的距离,打车费用,驾车时间的信息。
驾车路线规划是一个函数调用:
RoutePlanner(origin, destination, city)
origin:出发的地点
destination:到达的地点
city:查询的城市
使用时遵循以下格式<tool_call>route_plan(**kwargs)</tool_call>,如<tool_call>route_plan(origin="故宫", destination="颐和园", city="北京")</tool_call>
注意该工具只能用于同一个城市的两个地点之间的路线查询。

4.航班查询工具是一个函数调用,使用方法如下:
FlightSearch(departure_city_name,arrival_city_name,depart_date,**kwargs)
departure_city_name:必填参数,出发地所在城市名,完整城市名称,如“上海市”
arrival_city_name: 必填参数,到达地所在城市名,完整城市名称,如“杭州市”
depart_date:出发日期,如"2025-01-03"
is_transfer:是否中转,如果要查询中转航班,设置成1,搜索直飞,设置成0,必填参数
depart_time_earliest:出发最早时间,如07:00
depart_time_latest:出发最晚时间,如07:30
airline_name:可选参数,填入指定的航司名称(必须规范使用4个字的简称),支持多家航司,如"中国国航,东方航空"
departure_airport_name:起飞机场,可选参数
arrival_airport_name:降落机场,可选参数
min_sale_price:最低售价,单位:元,可选参数
max_sale_price:最高售价,单位:元,可选参数
使用时遵循以下格式:<tool_call>flight_search(**kwargs)</tool_call>。kwargs只可以传入上面列举的参数,不可用其他。
如:<tool_call>flight_search(departure_city_name="武汉市", arrival_city_name="上海市", depart_date="2025-02-26")</tool_call>

5.酒店查询工具是一个函数调用,使用方法如下:
HotelSearch(city_name,checkin_date,checkout_date,**kwargs)
city_name:城市名称,完整城市名称,如“北京”,只可以使用地级市,必填参数
checkin_date:入住日期,如"2025-01-01",必填参数,用户未指明的话,默认用当前时间的日期,必填参数
checkout_date:离店日期(至少比入店日期晚一天),如"2025-01-03",必填参数,用户未指明的话,默认用当前时间的日期加一天,必填参数
hotel_name:酒店名称,用户没提到不需填写
brand_name:酒店品牌名,如“汉庭”,“如家”,可选参数,用户没提到不需填写
landmark:地标,如“故宫”,如果用户提到xx附近的酒店,直接把landmark设置成xx,用户如果没提到则不需要填写
distance_range:酒店到landmark的距离,单位:米,用户如果没提到不需要填写
price_low:预算下限,可选参数,用户没提到不需填写
price_high:预算上限,可选参数,用户没提到不需填写
level_name:星级,用户没提到不需填写
has_breakfast:是否包含早餐,1:包含,0:不包含,用户没提到不需填写
使用时遵循以下格式:<tool_call>hotel_search(**kwargs)</tool_call>。kwargs只可以传入上面列举的参数,不可用其他。
如:<tool_call>hotel_search(city_name="杭州", checkin_date="2025-05-01", checkout_date="2025-05-03", landmark="浙江大学")</tool_call>

6.火车票查询工具是一个函数调用,可以查询两个站点间的直达和中转信息,使用方法如下:
TrainSearch(depart_station, arrive_station, depart_date, **kwargs)
depart_station:出发地或者出发站名,如"北京","北京西",必填参数
arrive_station:到达地或者到达站名,如"武汉",必填参数
depart_date:出发日期,如"2025-01-01",必填参数
is_transfer:是否中转,如果要查询中转列车,设置成1,搜索直达,设置成0,必填参数
depart_time_earliest:查询的最早出发时间(需要带日期),如"2025-01-01 07:00"
depart_time_latest:查询的最晚出发时间(需要带日期),如"2025-01-01 08:00"
arrive_time_latest:最晚的到达时间(需要带日期),如"2025-01-02 06:00"
max_price:票价不超过的最大价格,单位元
seat_name:座席类型,支持的坐席有这些:(商务座,一等座,二等座,硬座,软座,硬卧,软卧),用户没提到不需填写
middle_station_name:中转的车站,期望从哪里中转,is_transfer=1时候才可以使用。
对于中转查询,使用is_transfer参数,无需自己分段查询。
举例:
直达查询:<tool_call>train_search(depart_station="北京", arrive_station="丽江", depart_date="2025-05-01",depart_time_latest="2025-05-01 14:00")</tool_call>
中转查询:<tool_call>train_search(depart_station="北京", arrive_station="丽江", depart_date="2025-05-01",depart_time_latest="2025-05-01 18:00",is_transfer=1)</tool_call>

二.如果涉及到多次使用工具,按照以下格式:
<TOOL_BEGIN>
<tool_call>...</tool_call>
...
<tool_call>...</tool_call>
<TOOL_END>

三. tools调用需要遵循的原则:
1. 如果你觉得【待判别的回复】给出的方案没有满足用户的需求,可以重新调用工具进行验证

[评估流程]
请严格按照以下流程进行评估:先判断【待判别的回复】是否完整(时间线上完整的出行方案),若答案完整再评估其逻辑,若逻辑正确最后按需求调用tools评估其信息真实性。
并根据以下情况得出结论:
if 【逻辑合理】 and 【答案完整】:
    if【信息真实】:
        if【约束满足】:
            if【需求满足】:
                if 【突发情况有备选方案】:
                    判别为 非常满意
                else:
                    判别为 非常满意但未应对突发情况
            else:
                判别为 基本满意但未满足用户特定需求
        else:
            判别为 基本满意但未满足用户特定约束
    else:
        判别为 不满意,信息不真实
else:
    判别为 不满意,逻辑不合理

[评估输出]
1.待判别的回复:<直接复制输出用户第一次输入的待判别的回复的内容(注意请不要自己生成,且不为工具生成的回复)>,
2.判别理由:根据[评估原则]与[评估流程]分析待判别的回复,给出分析过程与理由
3.判别结论:非常满意 或者 非常满意但未应对突发情况 或者 基本满意但未满足用户特定需求 或者 基本满意但未满足用户特定约束 或者 不满意,信息不真实 或者 不满意,逻辑不合理

"""


Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐