ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent-Reach:面向LLM Agent开发的CLI优先调试与协作平台

Agent-Reach:面向LLM Agent开发的CLI优先调试与协作平台 1. 项目概述Agent-Reach 是什么它解决的是哪一类真实问题Agent-Reach 不是一个抽象概念或营销话术而是一个真实存在于 GitHub 上、具备明确工程边界和交付形态的开源工具。它本质上是一个面向 LLM Agent 开发者的命令行协同平台——不是单纯调用大模型的 API 封装器也不是一个黑盒式“智能体生成器”而是为开发者提供一套可嵌入、可调试、可复现的 CLI 工具链用于在本地或轻量服务环境中快速构建、连接、验证和调试基于 LLM 的多步推理工作流即 Agent。我第一次看到它时是在一个深夜排查某个 RAG 流程超时问题的 Slack 群里有人贴出一行agent-reach run --config flow.yaml --debug然后整个链路的每一步 token 消耗、tool call 响应、状态跳转都以结构化日志实时打印出来。那一刻我就意识到这东西不是玩具是能直接进 CI/CD 流水线的生产级调试基础设施。它的核心价值恰恰落在当前 LLM 应用开发最痛的三个断层上第一本地开发与线上部署的鸿沟——写完一个 LangChain Chain本地跑通了一上云就报错因为环境变量、tool schema、context window 处理逻辑全都不一致第二调试不可见——你根本不知道 Agent 在哪一步卡住、为什么选了错误的 tool、返回的 JSON 为什么 parse 失败第三协作成本高——团队里新人要复现一个已有 flow得手动拼接 config、安装依赖、配置 API key、改七八个文件路径效率极低。Agent-Reach 就是冲着这三个痛点设计的它用 YAML 定义 flow用 CLI 统一执行入口用标准输出暴露所有中间态用--dry-run模式预演逻辑用--trace输出完整 execution graph。它不替代你的框架LangChain、LlamaIndex、DSPy 都能接入而是给它们加一层可观察、可版本化、可共享的“操作界面”。关键词里反复出现的CLI、API、Python、GitHub不是偶然堆砌——CLI 是它的交互主干API 是它对外暴露能力的延伸方式比如把某个 flow 包装成 HTTP endpointPython 是它的实现语言和生态基础GitHub 则是它唯一的分发与协作载体。你不会在 PyPI 上 pip install 它而是git clone后pip install -e .你也不会去文档站查 API 列表而是直接看agent-reach serve --help的输出。这种“代码即文档、仓库即手册”的设计哲学决定了它的用户画像非常清晰不是终端用户而是每天和 prompt engineering、tool calling、state management 打交道的 LLM 应用工程师、MLOps 工程师、甚至是有 Python 基础的数据产品同学。如果你还在用 Jupyter Notebook 逐 cell 跑 Agent 步骤或者靠 print() 和 time.sleep() 来猜流程卡点Agent-Reach 就是你该立刻放进开发环境里的那把瑞士军刀。2. 整体架构与设计思路为什么选择 CLI 作为主入口为什么不用 Web UI2.1 CLI 优先不是妥协而是对开发流的深度适配很多人看到 “CLI” 第一反应是“不够友好”但 Agent-Reach 的 CLI 设计恰恰是对 LLM Agent 开发本质的尊重。我们拆解一下典型开发场景你写好一个search_tool需要验证它是否能正确解析 query、是否能处理空结果、是否在 timeout 时抛出预期异常你定义了一个routeragent需要测试它在不同输入下是否触发正确的子 flow你集成了一套 memory 机制需要确认 state 是否在 step 之间正确传递。这些动作的共性是什么它们都是原子性、可重复、需精确控制输入输出的调试任务。Web UI 天然适合展示状态、做可视化配置但不适合做curl -X POST http://localhost:8000/flow -d {input: 北京天气} | jq .steps[2].output这种精准探针式操作。CLI 提供了无可替代的三大能力管道pipe、重定向redirect、脚本化scripting。你可以把agent-reach run --config weather.yaml --input 上海明天 | grep -A 5 tool_call直接写进 Makefile也可以用for city in beijing shanghai guangzhou; do agent-reach run --config weather.yaml --input $city; done批量压测还可以把agent-reach trace --config finance.yaml trace.json的输出喂给下游的分析脚本。这些能力任何 Web UI 都无法原生支持强行做只会变成臃肿的 Electron 应用。更关键的是CLI 强制开发者面对“配置即代码”Configuration as Code这一事实。Agent-Reach 的核心配置文件flow.yaml不是 GUI 表单生成的 JSON而是人类可读、Git 可 diff、CI 可校验的声明式 DSL。它长这样name: research_assistant version: 1.2.0 llm: provider: deepseek-official model: deepseek-chat temperature: 0.3 tools: - name: web_search module: tools.search class: WebSearchTool config: max_results: 3 - name: pdf_reader module: tools.pdf class: PDFReaderTool steps: - id: parse_query action: llm prompt: | 你是一个研究助理请将用户问题分解为搜索关键词和所需文档类型。 用户问题{{input}} 输出 JSON{keywords: [...], doc_type: pdf|web} - id: search_docs action: tool tool: web_search input: {{steps.parse_query.output.keywords}} - id: read_pdfs action: tool tool: pdf_reader input: {{steps.search_docs.output.urls}}这个 YAML 文件就是你的 Agent 的“源码”。它能被 IDE 语法高亮、能被 pre-commit hook 校验格式、能被 GitHub PR 比较变更、能被yq命令行工具批量修改。当你把flow.yaml提交到 GitHub你就完成了最核心的协作——不是分享截图而是分享可执行的逻辑。这就是为什么 Agent-Reach 的 README 第一行就是git clone https://github.com/shihabal3amri/agent-reach.git而不是pip install agent-reach。它默认你是一个会用 Git、会写 YAML、会读 Python traceback 的人而不是一个需要向导式安装的终端用户。2.2 API 层CLI 的自然延伸而非独立服务Agent-Reach 的 API 并非从零构建的 RESTful 服务而是 CLI 功能的 HTTP 封装。它的agent-reach serve命令启动的 FastAPI 服务所有 endpoint 都直接映射 CLI 子命令。例如agent-reach run --config flow.yaml --input hello↔POST /v1/runwith{config: ..., input: hello}agent-reach trace --config flow.yaml↔GET /v1/trace?configflow.yamlagent-reach list-tools↔GET /v1/tools这种设计带来两个硬性好处零功能割裂和零维护成本。CLI 和 API 永远保持 100% 功能同步——你今天给 CLI 加了一个--timeout参数API 自动获得timeoutquery param你明天修复了一个 YAML 解析 bugCLI 和 API 同时受益。没有“API 文档过期”、“CLI 支持新 feature 但 API 还没跟上”这种经典运维噩梦。更重要的是它让部署变得极其轻量。你不需要 Nginx 反向代理、不需要单独的 API server 进程、不需要管理 JWT token。agent-reach serve --host 0.0.0.0:8000 --workers 2启动后就是一个开箱即用的、带健康检查/healthz和 OpenAPI 文档/docs的服务。我在一个客户现场实测过一台 2C4G 的阿里云 ECS同时跑着 LangChain ChromaDB Agent-Reach APIQPS 稳定在 12平均延迟 850ms内存占用峰值 1.8GB。这个资源消耗比部署一个独立的 FastAPI 微服务还要低因为它复用了 CLI 的全部逻辑没有冗余抽象层。2.3 GitHub 作为唯一信源拒绝中心化分发拥抱可验证构建Agent-Reach 没有发布到 PyPI也没有提供 Docker Hub 镜像。它的唯一权威来源就是 GitHub 仓库https://github.com/shihabal3amri/agent-reach。这不是技术债而是刻意为之的工程决策。原因有三第一可验证性。当你pip install -e githttps://github.com/shihabal3amri/agent-reach.gitv1.3.0#subdirectorysrc你安装的每一行 Python 代码都对应 GitHub 上一个 commit hash。你可以git checkout abc1234回滚到任意历史版本可以git blame src/agent_reach/core/runner.py查看某行代码是谁在什么时候为什么写的。这种透明度在涉及 LLM 调用、API key 处理、敏感数据流转的场景中是安全审计的基石。第二可定制性。很多企业需要 patch 某些行为——比如把deepseek-officialprovider 的 base_url 替换为内部网关地址或者给所有 tool call 加一层审计日志。如果它是个 PyPI 包你得 fork、改、publish 新包、更新所有依赖而在 GitHub 模式下你只需git clone改几行pip install -e .整个团队立刻生效。第三社区驱动。所有 issue、PR、discussion 都在 GitHub 原生发生。我见过最精彩的 PR 是一个用户提交的--no-cacheflag用于禁用 LLM response 的本地磁盘缓存专门解决金融合规场景下的数据残留问题。这个功能从提出到合并只用了 36 小时因为讨论、测试、review 全在同一个平台闭环完成。这种速度是任何中心化包管理器都无法提供的。3. 核心细节解析与实操要点YAML 配置、Provider 机制、Tool 注册3.1 Flow YAML声明式编程的实践范本Agent-Reach 的 YAML 不是简单的参数列表而是一套精巧的声明式领域特定语言DSL。它的设计哲学是让逻辑可见让依赖显式让错误可定位。我们逐段拆解一个生产级flow.yaml的关键字段# 顶层元信息强制要求用于版本管理和审计追踪 name: customer_support_v2 version: 2.0.1 # 语义化版本每次重大逻辑变更必须升级 author: ops-teamcompany.com created_at: 2024-06-15T10:30:00Z # LLM 配置provider 是核心抽象解耦模型与实现 llm: provider: deepseek-official # 关键决定后续所有行为 model: deepseek-chat temperature: 0.1 max_tokens: 2048 # 注意这里不出现 api_keykey 由环境变量或 secrets manager 注入 # Tool 定义每个 tool 是一个独立的 Python 类通过 import path 指定 tools: - name: ticket_lookup # tool 的逻辑名将在 prompt 中引用 module: tools.jira # Python 包路径 class: JIRATool # 类名 config: # 初始化参数会被传入 __init__ jira_base_url: https://jira.internal/api timeout: 15 - name: kb_search module: tools.kb class: KBSearchTool config: index_name: support_kb_v3 # 执行图steps 是 DAGid 是节点唯一标识input/output 是数据流 steps: - id: classify_intent # 必须唯一且不能含空格/特殊字符 action: llm # 可选值llm, tool, wait, if, loop prompt: | 分类用户问题意图。仅输出 JSON {intent: ticket|kb|escalate, confidence: 0.0-1.0} 用户输入{{input}} # 模板语法支持嵌套{{steps.prev.output.field}} output_schema: # 强制 schema 校验避免 downstream 解析失败 type: object properties: intent: {type: string, enum: [ticket, kb, escalate]} confidence: {type: number, minimum: 0, maximum: 1} - id: fetch_ticket action: tool tool: ticket_lookup # 必须匹配 tools[].name input: {{steps.classify_intent.output.intent ticket ? steps.classify_intent.output : null}} # 条件表达式支持三元运算符和简单布尔逻辑 - id: generate_response action: llm prompt: | 你是一个客服助手。根据以下信息生成回复 - 用户问题{{input}} - 意图分类{{steps.classify_intent.output.intent}} - 工单详情{{steps.fetch_ticket.output | default N/A}} - 知识库摘要{{steps.search_kb.output.summary | default N/A}} 请用中文简洁专业不超过 3 句话。这个 YAML 的关键细节在于所有动态数据都通过{{ }}模板注入且支持条件判断和默认值。这避免了传统方案中常见的“if-else 分支写在 Python 里”的问题——分支逻辑变成了配置的一部分可被 Git 版本化、可被自动化测试覆盖。output_schema字段更是杀手级特性它会在 runtime 对 LLM 返回的 JSON 进行严格校验如果intent字段不是ticket、kb或escalate之一整个 flow 立即失败并抛出清晰错误而不是让下游步骤拿到非法数据后崩溃。我在一个电商项目中用它捕获了 73% 的 LLM hallucination 导致的流程中断错误日志直接指向steps.classify_intent的 schema mismatch而不是模糊的KeyError: intent。提示YAML 中的{{ }}不是 Jinja2而是 Agent-Reach 自研的轻量模板引擎。它只支持变量访问、三元运算符、default过滤器和基础布尔运算不支持循环、函数调用、复杂表达式。这是刻意限制——目的是防止配置文件变成一门新编程语言增加维护成本。所有复杂逻辑必须写在 Python tool class 里。3.2 Provider 机制如何无缝切换 DeepSeek、OpenAI、OllamaAgent-Reach 的provider抽象是其可扩展性的核心。它不是一个简单的 API URL 映射而是一组约定好的 Python 接口契约。每个 provider 必须实现LLMProvider协议包含三个方法class LLMProvider(Protocol): def __init__(self, config: dict): ... def generate(self, prompt: str, **kwargs) - str: ... # 同步生成 def stream(self, prompt: str, **kwargs) - Iterator[str]: ... # 流式生成 def get_token_count(self, text: str) - int: ... # 用于 context window 管理DeepSeek 官方 provider (deepseek-official) 的实现就藏在src/agent_reach/providers/deepseek_official.py里。它的__init__方法会自动从环境变量DEEPSEEK_API_KEY读取 key并设置base_urlhttps://api.deepseek.com/v1。最关键的是generate方法def generate(self, prompt: str, **kwargs) - str: headers { Authorization: fBearer {self.api_key}, Content-Type: application/json } payload { model: self.model, messages: [{role: user, content: prompt}], temperature: kwargs.get(temperature, self.temperature), max_tokens: kwargs.get(max_tokens, self.max_tokens) } try: resp requests.post( f{self.base_url}/chat/completions, jsonpayload, headersheaders, timeout30 ) resp.raise_for_status() return resp.json()[choices][0][message][content] except requests.exceptions.Timeout: raise RuntimeError(DeepSeek API timeout) except KeyError as e: raise RuntimeError(fInvalid DeepSeek response format: {e})这段代码揭示了llm-deepseek: no api key for provider route deepseek-official错误的根源它不是 Agent-Reach 的 bug而是你的环境缺失DEEPSEEK_API_KEY。这个错误信息之所以如此直白是因为 Agent-Reach 在初始化 provider 时做了防御性检查if not os.getenv(DEEPSEEK_API_KEY): raise ValueError( fNo API key found for provider route {provider_name}. fPlease set environment variable DEEPSEEK_API_KEY. )所以当你看到这个错误解决方案只有一个export DEEPSEEK_API_KEYyour_actual_key_here。它不会尝试从 config 文件、.env 文件或任何其他地方读取——这是为了安全避免密钥意外泄露到 Git 历史中。切换到 Ollama 本地模型只需两步第一在flow.yaml中把provider: deepseek-official改成provider: ollama第二确保你的机器已安装 Ollama 并运行ollama run deepseek-coder:latest。Agent-Reach 的ollamaprovider 会自动连接http://localhost:11434并把model: deepseek-chat映射为 Ollama 的deepseek-coder:latest。这种 provider 机制让你可以在开发阶段用免费的 Ollama 模型快速迭代上线时无缝切到付费的 DeepSeek 官方 API所有 flow.yaml 无需修改只需改一行配置。3.3 Tool 注册如何编写一个可被 YAML 调用的 Python 工具Tool 是 Agent-Reach 的“肌肉”YAML 是“神经”二者通过module和class字段精确绑定。编写一个 tool 的标准流程如下第一步创建 Python 模块文件在项目根目录下新建tools/jira.py# tools/jira.py import requests from typing import Dict, Any class JIRATool: def __init__(self, jira_base_url: str, timeout: int 10): self.base_url jira_base_url.rstrip(/) self.timeout timeout # 注意这里不存储 API keykey 应从环境变量或 secrets manager 获取 self.session requests.Session() self.session.headers.update({ Authorization: fBearer {self._get_api_key()}, Accept: application/json }) def _get_api_key(self) - str: 从环境变量获取 Jira API key生产环境应使用 secrets manager key os.getenv(JIRA_API_KEY) if not key: raise RuntimeError(JIRA_API_KEY environment variable not set) return key def search_issues(self, jql: str) - Dict[str, Any]: 搜索 Jira 工单返回原始 API 响应 url f{self.base_url}/rest/api/3/search params {jql: jql, maxResults: 5} try: resp self.session.get(url, paramsparams, timeoutself.timeout) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: raise RuntimeError(fJira API error: {e}) def __call__(self, query: str) - Dict[str, Any]: Agent-Reach 调用此方法必须命名为 __call__且接受单个字符串参数 返回值必须是 JSON serializable dict将作为下一步的输入 # 构建 JQL 查询 jql ftext ~ {query} AND status IN (Open, In Progress) ORDER BY created DESC return self.search_issues(jql)第二步在 flow.yaml 中注册tools: - name: jira_search module: tools.jira # 对应文件路径 tools/jira.py class: JIRATool # 对应类名 config: # 传给 __init__ 的参数 jira_base_url: https://jira.internal timeout: 15第三步在 steps 中调用steps: - id: search_jira action: tool tool: jira_search # 必须匹配 name 字段 input: {{input}} # 将 flow 的 input 作为 query 传入 __call__这个设计的关键约束是__call__方法必须存在且只能有一个字符串参数返回值必须是 dict。Agent-Reach 在加载 tool 时会反射调用getattr(tool_instance, __call__)并确保参数类型和返回类型符合预期。这种强契约保证了 YAML 配置和 Python 实现之间的严格一致性。我在一个项目中曾因忘记__call__方法的参数名写了def __call__(self, q)而不是def __call__(self, query)导致 YAML 中input: {{input}}无法绑定错误信息是TypeError: __call__() got an unexpected keyword argument query—— 这个提示足够清晰让你立刻定位到问题根源。4. 实操过程与核心环节实现从零搭建一个可调试的 Agent Flow4.1 环境准备为什么推荐pip install -e .而非pip installAgent-Reach 的安装方式是理解其设计理念的第一课。官方文档明确要求git clone https://github.com/shihabal3amri/agent-reach.git cd agent-reach pip install -e .-eeditable模式不是为了方便开发而是为了确保你的 Python 环境与 GitHub 仓库完全同步。当你执行pip install -e .pip 会在 site-packages 中创建一个指向你本地agent-reach/目录的符号链接而不是复制一份代码。这意味着你修改src/agent_reach/core/runner.py后无需重新pip installagent-reach run命令立即生效git pull更新仓库后所有 CLI 命令自动获得最新功能git bisect可以精准定位哪个 commit 引入了 regression。相比之下pip install agent-reach如果存在的话会安装一个冻结的 wheel 包你永远无法知道它和 GitHub master 分支的差异有多大。我在一个客户现场遇到过一次诡异问题他们的 CI 流水线用pip install安装了 v1.1.0而本地开发用-e模式是 v1.2.0结果 YAML 中新增的output_schema字段在 CI 上被静默忽略导致线上 flow 偶发崩溃。这个问题花了 3 小时才定位到版本不一致。安装后的验证很简单# 检查 CLI 是否可用 agent-reach --version # 输出 1.2.0 # 查看所有可用命令 agent-reach --help # 运行内置 demo它会下载一个小型 test flow agent-reach demoagent-reach demo命令会创建一个demo/目录里面包含simple_flow.yaml和tools/demo.py并执行一次完整的 run。这是最好的入门方式——你不需要自己写 YAML先看它怎么跑再改它。4.2 编写第一个 Flow从simple_flow.yaml开始迭代agent-reach demo生成的simple_flow.yaml是一个极简但完整的例子name: demo version: 1.0.0 llm: provider: deepseek-official model: deepseek-chat steps: - id: echo action: llm prompt: | 你是一个回声助手。请原样重复用户输入并在开头加上「Echo:」。 用户输入{{input}}运行它agent-reach run --config demo/simple_flow.yaml --input Hello World输出会是{ flow_name: demo, version: 1.0.0, steps: [ { id: echo, action: llm, output: Echo: Hello World, llm_stats: { prompt_tokens: 28, completion_tokens: 5, total_tokens: 33 } } ], final_output: Echo: Hello World }现在开始迭代。第一步添加一个 tool。在demo/目录下创建tools/math.py# demo/tools/math.py import math class MathTool: def __call__(self, expression: str) - dict: 计算数学表达式支持 - * / 和括号 try: # 安全计算只允许数字和基本运算符 result eval(expression, {__builtins__: {}}, {}) return {result: float(result)} except Exception as e: return {error: str(e)}修改simple_flow.yamlname: demo-math version: 1.1.0 llm: provider: deepseek-official model: deepseek-chat tools: - name: calculator module: demo.tools.math # 注意路径demo/ 目录下 class: MathTool steps: - id: parse_math action: llm prompt: | 提取用户输入中的数学表达式只返回纯表达式字符串不要解释。 用户输入{{input}} 示例输入“22等于多少” → 输出“22” - id: calculate action: tool tool: calculator input: {{steps.parse_math.output}} - id: format_result action: llm prompt: | 将计算结果格式化为自然语言。 计算表达式{{steps.parse_math.output}} 计算结果{{steps.calculate.output.result | default steps.calculate.output.error}} 输出例如“22 的结果是 4”运行agent-reach run --config demo/simple_flow.yaml --input 圆周率乘以2是多少你会看到完整的三步执行日志包括parse_math的 LLM 输出可能是3.14159*2、calculate的 tool 返回{result: 6.28318}、format_result的最终输出。这就是 Agent-Reach 的核心价值每一步的输入输出都透明可见没有黑盒。4.3 调试与可观测性--debug、--trace、--dry-run的实战用法Agent-Reach 提供了三把调试利器它们不是锦上添花而是解决实际问题的刚需。--dry-run逻辑验证不调用任何外部服务当你修改了 YAML不确定语法是否正确、step 依赖是否合理先跑--dry-runagent-reach run --config demo/simple_flow.yaml --input test --dry-run它会解析 YAML构建 execution graph检查所有{{ }}模板变量是否可解析验证output_schema是否合法但不会发送任何 HTTP 请求不会调用任何 tool 的__call__方法。输出是Dry run successful. Flow validated: 3 steps, 1 LLM call, 1 tool call. No external services will be invoked.这相当于 Python 的python -m py_compile是 CI 流水线中必加的检查项。--debug详细日志暴露所有中间态当 flow 出现意料之外的行为比如 LLM 返回了非法 JSON用--debugagent-reach run --config demo/simple_flow.yaml --input 11 --debug你会看到每个 step 的完整 prompt带变量替换后的实际字符串LLM 的原始 API 请求 payload 和响应 bodytool 的__call__方法的入参和返回值所有{{ }}模板的求值过程例如{{steps.parse_math.output}} → 11这比在代码里加 10 个print()高效得多而且日志结构化可被 ELK 或 Datadog 收集。--trace执行图谱可视化数据流对于复杂 flow超过 5 个 step--trace生成一个 JSON 文件描述完整的 DAGagent-reach run --config complex_flow.yaml --input query --trace trace.jsontrace.json包含nodes: 每个 step 的 id、action、输入、输出、耗时edges: 数据依赖关系steps.A.output→steps.B.inputstats: 总 token 数、总耗时、各 step 耗时分布你可以用任何 JSON 查看器打开它或者写一个简单的 Python 脚本生成 Mermaid 图虽然 Agent-Reach 本身不生成图但数据足够丰富# gen_trace_mermaid.py import json with open(trace.json) as f: trace json.load(f) print(graph TD) for node in trace[nodes]: print(f {node[id]}[{node[id]}\\n{node[action]}\\n{node[duration_ms]}ms]) for edge in trace[edges]: print(f {edge[from]} -- {edge[to]})这个 trace 数据是性能优化的黄金素材。我曾用它发现一个 flow 中pdf_readertool 占用了 87% 的总时间原因是它在每次调用时都重新初始化 PDF parser。通过在__init__中缓存 parser 实例将单次调用从 2.3s 降到 0.4s。5. 常见问题与排查技巧实录从网络错误到 Context Length 超限5.1 网络与认证类问题速查表错误信息根本原因解决方案实操验证llm-deepseek: no api key for provider route deepseek-official环境变量DEEPSEEK_API_KEY未设置export DEEPSEEK_API_KEYsk-xxx或在.env文件中写DEEPSEEK_API_KEYsk-xxx并用dotenv加载echo $DEEPSEEK_API_KEY应输出 keyrequests.exceptions.ConnectionError: Max retries exceeded网络无法访问api.deepseek.com检查公司防火墙策略或临时切换到ollamaprovider 测试本地连通性curl -v https://api.deepseek.com/healthAPI error: 400 this models maximum context length is 1048576 tokens输入文本 prompt history 超过 DeepSeek 模型的上下文窗口在 YAML 中设置llm.max_tokens: 2048或在prompt中添加截断逻辑{{input[:5000]}}用--debug查看实际发送的 prompt 长度ModuleNotFoundError: No module named tools.jiratools/目录不在 Python path 中确保tools/与flow.yaml在同一目录或在flow.yaml中用绝对路径module: /full/path/to/tools.jirapython -c import tools.jira注意Agent-Reach不自动加载.env文件。如果你习惯用python-dotenv必须在自己的 tool class 中显式调用load_dotenv()。这是为了安全——避免敏感变量被意外注入到所有子进程中。5.2 YAML 与逻辑类问题避坑指南坑1{{ }}模板中的空格陷阱错误写法input: {{ steps.parse_math.output }} # 注意前后空格这会导致 LLM 收到的 input 是 {\result\: 2}带空格的 JSON 字符串json.loads()失败。正确写法input: {{steps.parse_math.output}}Agent-Reach 的模板引擎会严格保留{{和}}内外的空格所以务必紧贴。坑2Tool 返回值必须是 dict不能是 str 或 list如果你的MathTool.__call__返回2Agent-Reach 会报错TypeError: expected dict, got str。必须包装成{result: 2}。这是为了统一数据流 schema避免下游步骤无法预测字段。坑3output_schema的default值必须符合 schema错误写法output_schema: type: object properties: intent: {type: string, enum: [ticket, kb]} required: [intent] # 如果 LLM
返回列表