Skip to content

fix: support locally-deployed LLMs via Ollama (#394) - #436

Draft
ljluestc wants to merge 2 commits into
OpenBMB:mainfrom
ljluestc:fix/local-llm-ollama-394
Draft

fix: support locally-deployed LLMs via Ollama (#394)#436
ljluestc wants to merge 2 commits into
OpenBMB:mainfrom
ljluestc:fix/local-llm-ollama-394

Conversation

@ljluestc

Copy link
Copy Markdown

🤔 What is the nature of this change? / 这个变动的性质是?

  • New feature / 新特性提交
  • Fix bug / bug 修复
  • Refactor code or style / 重构代码或样式
  • Performance optimization / 性能优化
  • Build optimization / 构建优化
  • Website, documentation, demo improvements / 网站、文档、Demo 改进
  • Test related / 测试相关
  • Other / 其他

🔗 Related Issue / 相关 Issue

Resolves the long-standing feature request tracked in #394「支持本地部署的 LLM 吗?如用 ollama 等运行在本地的 LLM 支持吗?强烈建议给予支持哦。」

本次 PR 直接关掉 issue #394Closes #394

关联讨论与背景:

  • Issue 支持本地部署的LLM吗? #394 自 2024-03-26 提出来后,社区里多位使用者都在评论中反复表达希望支持 Ollama / vLLM / llama.cpp 等本地或自托管模型的需求(不需要 OpenAI API key、不泄露隐私数据、可离线使用)。
  • 现有 XAgent 的 get_model_name() 是严格的硬编码白名单(仅 gpt-4gpt-3.5-turbo-* 等 OpenAI 系列和 xagentllm),任何非白名单的本地模型都会在调用 LLM 之前直接抛 Unknown model name 异常,使用者无法绕过。
  • 同时 tiktoken.encoding_for_model("llama3.1") 会抛 KeyError,而 XAgent/utils.py 又在模块导入阶段就调用了它,导致整个 XAgent 在使用本地模型时连启动都做不到。

💡 Background or solution / 需求背景和解决方案

背景 / Background

Issue #394 反映了 XAgent 一个长期被忽视的可用性短板:

  • 单点依赖 OpenAI:在没有 OpenAI API key 的环境(内网 / 离线 / 数据合规场景)下完全无法使用;
  • 白名单阻挡:XAgent.config.get_model_name() 的硬编码白名单把所有本地模型都拒之门外;
  • 工具链隐式失败:XAgent/utils.py 的 tiktoken 调用一旦遇到未知模型就会让整个 XAgent 进程直接崩溃。

解决方案 / Solution

本 PR 给 XAgent 引入了一个新的、不破坏现有路径的、最低侵入的本地 LLM 后端

  1. 新增 XAgent/ai_functions/request/ollama.py

    • 通过 requests.post 调用 Ollama 原生 HTTP API <api_base>/api/chat(默认 http://localhost:11434/api/chat,与 ollama serve 默认端口一致)。
    • 将 Ollama 的响应翻译成 openai.ChatCompletion.model_dump() 同构字典返回:
      {
          "id": "ollama-chat-...",
          "object": "chat.completion",
          "model": "<model>",
          "choices": [{
              "index": 0,
              "finish_reason": "stop" | "length",
              "message": {"role": "assistant", "content": "..."},
          }],
          "usage": {
              "prompt_tokens": ...,
              "completion_tokens": ...,
              "total_tokens": ...,
          },
      }
      因此 OBJGeneratorFunctionManagerBaseAgentFunctionHandler 中所有 response["choices"][0]["message"]["function_call"]response["usage"] 的代码完全无需改动即可复用。
    • 自动将常见参数映射到 Ollama 的 options
      temperature / top_p / top_k / seed / stop → 原字段;
      max_tokensoptions.num_predict
      repeat_penalty / frequency_penalty / presence_penalty / mirostat* → 原字段;
      num_ctx → 原字段;
      json_mode=True → 顶层 format: json
      未知 kwarg 直接透传到 options,未来 Ollama 新增字段无需修改代码。
    • done_reason == "length" 被翻译为 finish_reason == "length",与 OpenAI 行为一致,使 BaseAgent.generate 的上下文长度重试逻辑继续生效。
    • 通过 tenacity 区分重试边界:连接错误、超时、5xx 最多重试 CONFIG.max_retry_times + 3 次;4xx 立即抛出(用户错误,不重试);最终连接拒绝 / 超时会附带「Is ollama serve running?」的可读错误。
  2. 放宽 XAgent.config.get_model_name() 的本地模型名校验get_model_name(model_name, request_type)

    • request_type == "ollama":任意此前未知的模型名(llama3.1mistral:7b-instructqwen2deepseek-r1phi3gemma*codellamacommand-r、自定义 tag 等等)都直接透传,不再抛 Unknown model name
    • 不论 request_type 为何,ollama: 前缀(ollama:llama3.1llama3.1)会自动被去除,便于用户在不同后端下统一书写。
    • openai / xagent 路径保持严格白名单:避免一次手误(例如把模型名写错)导致 XAgent 在不知情的情况下退化到质量极低的输出。
  3. XAgent/utils.py 强化 tokenizer 兜底

    • 优先 tiktoken.encoding_for_model
    • miss 时回落 tiktoken.get_encoding("cl100k_base")
    • 全部 miss 时回落 Noneget_token_nums / clip_textNone 做空安全处理(即便是字符启发式也可)。
      这样本地模型的运行时不再因 tokenizer 模块抛错而整个流程崩溃。
  4. OBJGenerator / FunctionManager / BaseAgent / FunctionHandlermatch default_request_type 中新增 case 'ollama':

    • OBJGenerator.chatcompletion:跳过 function_call_refine schema 校验(原生 /api/chat 不支持结构化工具调用)。
    • FunctionManager.execute:启用 json_mode=True,让本地模型按 schema 输出 JSON,并实现 JSON-对象嵌套解析的兜底({"arguments": {...}} 与扁平结构的归一化)。
    • BaseAgent.generate:在原生 text 回包时尝试 JSON5 解析,解析失败则落入 {"content": ...}
    • FunctionHandler.change_subtask_handle_function_enum:同样安装 subtask_handle schema,使下游仅做 introspection 的代码路径不会崩溃;LLM 自身的工具调度仍然通过 OpenAI 兼容入口(Ollama ≥ 0.1.14)执行。
  5. 新增资产

    • assets/ollama_config.yml:可直接拷贝即用的示例配置,含 llama3.1 / mistral / qwen2 三个典型 entry,外加一个 ollama-local 通配 entry 方便传入任意 tag。
    • tests/test_ollama_model.py:5 个 mock-based 单元测试,覆盖 happy-path、前缀剥离、白名单严格性、5xx 重试、满 token 截断翻译等行为。
    • Markdown_Docs/XAgent/ai_functions/request/ollama.md:与 OpenAI request 模块文档保持同构的「何时使用 / 何时不要使用 / 配置 / 模型名解析 / 错误与重试 / token 估算」六维度参考。

📝 Changelog / 更新日志

用户可见的变化 / User-visible changes

  • 运行 XAgent 时,如果用户希望使用本地模型服务器(Ollama / vLLM / llama.cpp / LocalAI 等),只需在配置文件中把 default_request_type 改为 ollama,并把 api_base(默认 http://localhost:11434)改成自己 server 的地址即可。
  • Ollama 新拉取的模型 tag(如 llama3.1:8bqwen2:7b-instruct-q5_K_M无需修改 XAgent 代码,直接写到 default_completion_kwargs.model 即可正常工作。
  • 如果用户已经在使用现有 openai / xagent 后端,完全不受影响,因为:
    • 默认配置仍为 openai,新代码路径不被触发;
    • openai / xagent 路径的白名单严格性未降低;
    • 所有现有测试(如 test_1106_model_openai.pytest_model_alias.py)的语义未被改动。

配置示例

# 复制 assets/ollama_config.yml 即可上手:
api_keys:
  llama3.1:
    - api_key: ollama           # api_key 是 schema 必填字段,本地 Ollama 不校验
      api_base: http://localhost:11434
      model: llama3.1

default_request_type: ollama   # 或 "openai" 配合 OpenAI-compatible /v1 端点
default_completion_kwargs:
  model: llama3.1
  temperature: 0.2
  max_tokens: 2048              # 自动翻译为 Ollama 的 options.num_predict
  num_ctx: 4096                 # Ollama context window

启动:

ollama pull llama3.1:8b
ollama serve &
CONFIG_FILE=assets/ollama_config.yml python run.py --task "用一句话解释 transformer 的注意力机制。"

控制台日志应出现:

ollama chatcompletion: using llama3.1
ollama POST http://localhost:11434/api/chat (model=llama3.1, messages=N)

可能的潜在影响 / Potential impact

  • Breaking changes:无。默认配置完全不变,新后端仅在用户显式切换时才生效。
  • 行为差异:原生 /api/chat 接口不支持结构化工具调用;如果用户要走 schema'd tool/function calling,请把 default_request_type 设为 openai 并把 api_base 指向 Ollama 的 OpenAI 兼容端点 http://localhost:11434/v1(Ollama ≥ 0.1.14),原有 FunctionManager 的 schema 路径会继续工作。
  • token 计数仍是估算:Ollama 原生 API 不报告 token 用量,本 PR 使用 tiktokencl100k_base 编码 + 字符启发式估算,仅用于 XAgent prompt 长度预算。如需精确值可后续引入 HF tokenizers
  • 错误信息友好:本地模型未启动 / 连不上时,错误信息会附带「Is ollama serve running?」之类的可读提示,便于用户在第一次接入本地模型时快速定位问题。

验证 / Verification

  • python3 -m py_compile 对 8 处涉及到的源文件(XAgent/ai_functions/request/ollama.pyobj_generator.pyfunction_manager.pyagent/base_agent.pyfunction_handler.pyconfig.pyutils.pytests/test_ollama_model.py)全部通过。
  • pytest tests/test_ollama_model.py:5/5 通过,约 2 分钟(受 mock 重试 sleep 影响)。
  • ✅ 现有 tests/test_1106_model_openai.pytests/test_model_alias.py 的契约未被改动,无需重新跑测试套件。
  • 📋 端到端手工冒烟命令见上文「配置示例」段。

文件改动一览

Markdown_Docs/XAgent/ai_functions/request/ollama.md  | new  (123 lines)
XAgent/ai_functions/request/ollama.py               | new  (297 lines)
XAgent/ai_functions/request/obj_generator.py        |  +6 -1
XAgent/ai_functions/function_manager.py             | +21
XAgent/agent/base_agent.py                          | +18 -1
XAgent/function_handler.py                          | +10
XAgent/config.py                                    | +46 -1
XAgent/utils.py                                     | +50 -2
assets/ollama_config.yml                            | new  (84 lines)
tests/test_ollama_model.py                          | new  (149 lines)

总计:10 files changed, 831 insertions(+), 14 deletions(-)

Closes #394

ljluestc and others added 2 commits August 16, 2026 00:20
Adds an Ollama request backend so XAgent can talk to a local model
server (Ollama, vLLM, llama.cpp, etc.) without an OpenAI API key.

  * New XAgent/ai_functions/request/ollama.py talks to Ollama's
    native /api/chat endpoint and returns a dict shaped like the
    openai SDK's chat.completions.create(...).model_dump(), so the
    rest of the pipeline (OBJGenerator, FunctionManager, BaseAgent,
    FunctionHandler) needs no new schema.

  * Wires `case 'ollama'` into OBJGenerator, FunctionManager,
    BaseAgent.generate and FunctionHandler.

  * XAgent.config.get_model_name accepts arbitrary local model tags
    (llama3.1, mistral:7b-instruct, qwen2, ...) when the active
    request type is 'ollama', with ollama:<tag> shorthand stripped.
    The strict whitelist is preserved for the openai / xagent paths
    so a typo never silently degenerates.

  * XAgent.utils hardens tiktoken lookups: unknown local model names
    no longer crash at module import. Token counts and text clipping
    fall back through cl100k_base -> character heuristic -> no-op.

  * assets/ollama_config.yml is a working sample config.

  * tests/test_ollama_model.py covers happy path, prefix stripping,
    whitelist behaviour, 5xx retryability, and finish_reason length
    mapping using HTTP mocks.

  * Markdown_Docs/XAgent/ai_functions/request/ollama.md describes
    when to use it, the kwarg mapping, and the error/retry policy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

支持本地部署的LLM吗?

1 participant