fix: support locally-deployed LLMs via Ollama (#394) - #436
Draft
ljluestc wants to merge 2 commits into
Draft
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤔 What is the nature of this change? / 这个变动的性质是?
🔗 Related Issue / 相关 Issue
Resolves the long-standing feature request tracked in #394 — 「支持本地部署的 LLM 吗?如用 ollama 等运行在本地的 LLM 支持吗?强烈建议给予支持哦。」
本次 PR 直接关掉 issue #394:
Closes #394。关联讨论与背景:
get_model_name()是严格的硬编码白名单(仅gpt-4、gpt-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 一个长期被忽视的可用性短板:
XAgent.config.get_model_name()的硬编码白名单把所有本地模型都拒之门外;XAgent/utils.py的 tiktoken 调用一旦遇到未知模型就会让整个 XAgent 进程直接崩溃。解决方案 / Solution
本 PR 给 XAgent 引入了一个新的、不破坏现有路径的、最低侵入的本地 LLM 后端:
新增
XAgent/ai_functions/request/ollama.pyrequests.post调用 Ollama 原生 HTTP API<api_base>/api/chat(默认http://localhost:11434/api/chat,与ollama serve默认端口一致)。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": ..., }, }OBJGenerator、FunctionManager、BaseAgent、FunctionHandler中所有response["choices"][0]["message"]["function_call"]和response["usage"]的代码完全无需改动即可复用。options:temperature/top_p/top_k/seed/stop→ 原字段;max_tokens→options.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 立即抛出(用户错误,不重试);最终连接拒绝 / 超时会附带「Isollama serverunning?」的可读错误。放宽
XAgent.config.get_model_name()的本地模型名校验(get_model_name(model_name, request_type))request_type == "ollama":任意此前未知的模型名(llama3.1、mistral:7b-instruct、qwen2、deepseek-r1、phi3、gemma*、codellama、command-r、自定义 tag 等等)都直接透传,不再抛Unknown model name。request_type为何,ollama:前缀(ollama:llama3.1→llama3.1)会自动被去除,便于用户在不同后端下统一书写。openai/xagent路径保持严格白名单:避免一次手误(例如把模型名写错)导致 XAgent 在不知情的情况下退化到质量极低的输出。XAgent/utils.py强化 tokenizer 兜底tiktoken.encoding_for_model;tiktoken.get_encoding("cl100k_base");None,get_token_nums/clip_text对None做空安全处理(即便是字符启发式也可)。这样本地模型的运行时不再因 tokenizer 模块抛错而整个流程崩溃。
在
OBJGenerator/FunctionManager/BaseAgent/FunctionHandler的match default_request_type中新增case 'ollama':OBJGenerator.chatcompletion:跳过function_call_refineschema 校验(原生/api/chat不支持结构化工具调用)。FunctionManager.execute:启用json_mode=True,让本地模型按 schema 输出 JSON,并实现 JSON-对象嵌套解析的兜底({"arguments": {...}}与扁平结构的归一化)。BaseAgent.generate:在原生 text 回包时尝试 JSON5 解析,解析失败则落入{"content": ...}。FunctionHandler.change_subtask_handle_function_enum:同样安装subtask_handleschema,使下游仅做 introspection 的代码路径不会崩溃;LLM 自身的工具调度仍然通过 OpenAI 兼容入口(Ollama ≥ 0.1.14)执行。新增资产
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
default_request_type改为ollama,并把api_base(默认http://localhost:11434)改成自己 server 的地址即可。llama3.1:8b、qwen2:7b-instruct-q5_K_M)无需修改 XAgent 代码,直接写到default_completion_kwargs.model即可正常工作。openai/xagent后端,完全不受影响,因为:openai,新代码路径不被触发;openai/xagent路径的白名单严格性未降低;test_1106_model_openai.py、test_model_alias.py)的语义未被改动。配置示例
启动:
控制台日志应出现:
可能的潜在影响 / Potential impact
/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 路径会继续工作。tiktoken的cl100k_base编码 + 字符启发式估算,仅用于 XAgent prompt 长度预算。如需精确值可后续引入 HFtokenizers。Isollama serverunning?」之类的可读提示,便于用户在第一次接入本地模型时快速定位问题。验证 / Verification
python3 -m py_compile对 8 处涉及到的源文件(XAgent/ai_functions/request/ollama.py、obj_generator.py、function_manager.py、agent/base_agent.py、function_handler.py、config.py、utils.py、tests/test_ollama_model.py)全部通过。pytest tests/test_ollama_model.py:5/5 通过,约 2 分钟(受 mock 重试 sleep 影响)。tests/test_1106_model_openai.py与tests/test_model_alias.py的契约未被改动,无需重新跑测试套件。文件改动一览
总计:
10 files changed, 831 insertions(+), 14 deletions(-)。Closes #394