Skip to content

add claude support#1080

Merged
wzh1994 merged 5 commits intoLazyAGI:mainfrom
wzh1994:wzh/claude
Apr 7, 2026
Merged

add claude support#1080
wzh1994 merged 5 commits intoLazyAGI:mainfrom
wzh1994:wzh/claude

Conversation

@wzh1994
Copy link
Copy Markdown
Contributor

@wzh1994 wzh1994 commented Apr 7, 2026

📌 PR 内容 / PR Description

  • Add ClaudeChat supplier in supplier/claude.py — supports chat, streaming, and tool calls via Anthropic native Messages API (/v1/messages)
  • Add _prepare_request_data hook to OnlineChatModuleBase so subclasses can transform the request body before sending
  • Add format parameter ('openai' / 'anthropic') to LazyLLMPrompterBase.generate_prompt; deprecate return_dict=True with a one-time LOG.log_once warning
  • Add _generate_prompt_anthropic_impl to produce Anthropic-native prompt format (system as top-level field, no system message in messages list)
  • Add _message_format class variable to OnlineChatModuleBase (default 'openai'); ClaudeChat sets it to 'anthropic' so forward automatically picks the right prompt format
  • Add ClaudeChat docs (Chinese + English + example) to lazyllm/docs/module.py
  • Add test_claude_chat and test_claude_tool_call to tests/charge_tests/Models/test_chat.py

🎯 问题背景 / Problem Context

  • LazyLLM 缺少对 Anthropic Claude 的原生支持。Claude 的 API 格式(/v1/messages、独立 system 字段、input_schema 工具格式、SSE 事件类型)与 OpenAI 兼容格式差异较大,无法直接复用现有 supplier。

🔍 相关 Issue / Related Issue


✅ 变更类型 / Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Breaking change
  • Documentation
  • Performance optimization

🧪 如何测试 / How Has This Been Tested?

  1. Non-streaming chat: lazyllm.OnlineChatModule(source='claude', stream=False)('你好') returns a non-empty string
  2. Streaming chat: lazyllm.OnlineChatModule(source='claude', stream=True)('你好') streams tokens and returns the full merged response
  3. Tool call (non-stream + stream): pass OpenAI-format tools list; with FunctionCallFormatter the result is a dict containing tool_calls with correct name and arguments

📷 Demo (Optional)


⚡ 更新后的用法示例 / Usage After Update

import lazyllm

# Basic chat — set LAZYLLM_CLAUDE_API_KEY in environment
chat = lazyllm.OnlineChatModule(source='claude', model='claude-3-5-sonnet-20241022')
print(chat('Hello, who are you?'))

# Tool call
from lazyllm.components.formatter import FunctionCallFormatter
tools = [{'type': 'function', 'function': {
    'name': 'get_weather',
    'description': 'Get the current weather for a city',
    'parameters': {'type': 'object', 'properties': {'city': {'type': 'string'}}, 'required': ['city']}
}}]
chat = lazyllm.OnlineChatModule(source='claude', model='claude-3-5-sonnet-20241022', stream=False)
chat._formatter = FunctionCallFormatter()
result = chat('What is the weather in Beijing?', tools=tools)
# result: {'role': 'assistant', 'content': '', 'tool_calls': [{'id': '...', 'type': 'function', 'function': {'name': 'get_weather', 'arguments': '{"city": "Beijing"}'}}]}

🔄 重构对比(如适用)

Before

# generate_prompt 只支持 return_dict=True,输出 OpenAI 格式
data = self._prompt.generate_prompt(input=x, history=h, return_dict=True)
# data = {"messages": [{"role": "system", ...}, {"role": "user", ...}]}

After

# generate_prompt 新增 format 参数,子类通过 _message_format 类变量自动选择
# ClaudeChat._message_format = 'anthropic'
data = self._prompt.generate_prompt(input=x, history=h, format=self._message_format)
# data = {"system": "...", "messages": [{"role": "user", ...}]}  ← Anthropic 格式

⚠️ 注意事项 / Additional Notes

  • return_dict=True in generate_prompt is deprecated; use format='openai' instead. Existing callers continue to work unchanged (one-time warning emitted).
  • _prepare_request_data and _message_format are general-purpose extension points for future non-OpenAI suppliers.
  • Anthropic API differences vs OpenAI: auth header (x-api-key + anthropic-version), endpoint (/v1/messages), system as top-level field, max_tokens required, tool schema uses input_schema, stream uses SSE event types (content_block_delta etc.).

🧠 AI 参与情况 / AI Involvement

  • 未使用 AI
  • 使用 AI 辅助(局部代码)
  • 使用 AI 主导(大部分代码)

使用工具 / Tools Used:

  • Cursor
  • CodeX
  • ClaudeCode
  • OpenCode
  • 其他:

🤖 AI 生成过程 / AI Generation Process

初始 Prompt

帮我参考 lazyllm/module/llms/onlinemodule 的代码,加一个 claude 的 supplier。
APIkey 已在 ~/.zshrc 中用 LAZYLLM_CLAUDE_API_KEY 配置好,
key 对应的 base_url 是 https://cld.ppapi.vip/,模型名为 "claude-opus-4-6"。
主体代码用官方 API,测试代码用给定的 base_url 和 model。

AI 初始输出质量

  • 一次正确
  • 部分正确
  • 基本不可用

问题点:

AI 初始实现直接复用了 OpenAI 兼容路径(/v1/chat/completions),未处理 Anthropic 原生格式差异,导致请求失败。


🔧 人工干预过程 / Human Intervention

第一次修正

修改后的 Prompt / 操作:

claude 的接口格式是 v1/messages,不是 v1/chat/completion,
请重写 _get_chat_url 方法以适配

原因:

AI 未主动调研 Anthropic API 格式,默认沿用了 OpenAI 路径。需要明确指出端点差异。


第二次修正

修改后的 Prompt / 操作:

这个 forward 几乎重写了,可维护性不好,
能否基于基类的 forward,重写 _convert_msg_format

原因:

AI 第一版直接重写了整个 forward 方法,代码冗余且难以维护。人工指出应通过 hook 方法复用基类逻辑。


第三次修正

修改后的 Prompt / 操作:

我观察到你重写了 _prepare_request_data,原因是输入格式也有些许的不同。
现在,请你读 lazyllm/components/prompter 下面的 ChatPrompter、AlpacaPrompter 及其基类,
帮我给 generate_prompt 加一个参数:format,当 format='openai' 时是原来 return_dict 的逻辑,
当 format='anthropic' 时返回 anthropic 格式的输入。
之后,将 return_dict 参数标记为弃用,并提示用户使用 format='openai' 替代。
在 ClaudeChat 里面,去掉 _prepare_request_data,而是增加一个类变量 __format__,
在基类 generate_prompt 的时候,读取 __format__

原因:

人工发现 system 消息格式转换属于 prompt 层的职责,不应放在 request 构造层。通过引入 format 参数和 _message_format 类变量,使架构更清晰、可扩展。


最终策略总结

  • 遇到格式差异时,优先从架构层面找合适的 hook 点,而不是直接重写上层方法
  • Anthropic API 与 OpenAI 的差异需要在三个层面分别处理:prompt 格式(prompter 层)、请求头(_get_header)、响应解析(_convert_msg_format + _str_to_json
  • 类变量 _message_format 比运行时判断更清晰,子类只需声明意图

📊 AI vs 人工贡献占比

  • AI 生成代码占比:80%
  • 人工修改占比:20%

主要人工修改点:

  • 逻辑修正
  • 边界处理
  • 性能优化
  • 代码风格
  • 架构调整

⚠️ AI 问题记录 / AI Failure Cases

❌ 失败 Prompt 示例

帮我参考 lazyllm/module/llms/onlinemodule 的代码,加一个 claude 的 supplier

❌ 问题表现

  • AI 未主动调研 Anthropic API 格式,直接复用 OpenAI 兼容路径,导致请求 URL 错误
  • 第一版重写了整个 forward 方法,可维护性差
  • __format__ 命名触发 Python name mangling,子类无法正确覆盖(由 Gemini code review 发现)

✅ 改进后 Prompt

claude 的接口格式是 v1/messages,不是 v1/chat/completion,请重写 _get_chat_url 方法以适配

🧩 可复用经验 / Reusable Patterns

  • Prompt 模板:添加新 supplier 时,明确说明目标 API 的端点、认证方式、请求/响应格式差异,避免 AI 默认沿用 OpenAI 格式
  • 常见坑:双下划线类变量(__xxx__)在 Python 中有特殊含义或触发 name mangling,应使用单下划线前缀(_message_format
  • 推荐做法:先让 AI 生成骨架,再通过 prompt 逐步引导到正确的架构设计,比一次性给出完整需求效果更好

@wzh1994 wzh1994 requested review from a team as code owners April 7, 2026 05:24
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for the Anthropic Claude Messages API by adding the ClaudeChat module and updating the prompter logic to handle Anthropic's specific message format. Key changes include the addition of _generate_prompt_anthropic_impl in the prompter, a new format parameter to replace the deprecated return_dict, and the implementation of ClaudeChat with custom message and tool conversion logic. Feedback highlights potential issues with Python name mangling due to the use of format, the use of an invalid default model name (claude-opus-4-5), and an incorrect API endpoint for key validation.

VLM_MODEL_PREFIX = []
NO_PROXY = True
__lazyllm_registry_key__ = LLMType.CHAT
__format__ = 'openai'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using __format__ as a class attribute is problematic because double underscores trigger Python's name mangling. In the forward method (line 138), self.__format__ will always resolve to _LazyLLMOnlineChatModuleBase__format__ (i.e., 'openai'), even if a subclass like ClaudeChat defines its own __format__. Additionally, __format__ is a special Python method name. It should be renamed to something like _format or message_format to allow proper polymorphism.

Suggested change
__format__ = 'openai'
_format = 'openai'

runtime_model = model or self._model_name

params = {'input': __input, 'history': llm_chat_history, 'return_dict': True}
params = {'input': __input, 'history': llm_chat_history, 'format': self.__format__}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Access the renamed attribute _format instead of the mangled __format__ to ensure subclasses can provide their own format identifier to the prompter.

Suggested change
params = {'input': __input, 'history': llm_chat_history, 'format': self.__format__}
params = {'input': __input, 'history': llm_chat_history, 'format': self._format}


_ANTHROPIC_VERSION = '2023-06-01'
_DEFAULT_MAX_TOKENS = 4096
__format__ = 'anthropic'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Rename this to _format to match the change in the base class and avoid name mangling issues.

Suggested change
__format__ = 'anthropic'
_format = 'anthropic'

def __init__(self, base_url: Optional[str] = None, model: Optional[str] = None,
api_key: str = None, stream: bool = True, return_trace: bool = False, **kwargs):
base_url = base_url or 'https://api.anthropic.com/v1/'
model = model or 'claude-opus-4-5'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

claude-opus-4-5 is not a valid Anthropic model name. The current flagship models are claude-3-opus-20240229 or claude-3-5-sonnet-20241022. Using a non-existent model name as the default will cause API requests to fail with a 404 or 400 error.

Suggested change
model = model or 'claude-opus-4-5'
model = model or 'claude-3-5-sonnet-20241022'

Comment on lines +126 to +132
def _validate_api_key(self):
try:
models_url = urljoin(self._base_url, 'models')
response = requests.get(models_url, headers=self._header, timeout=10)
return response.status_code == 200
except Exception:
return False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Anthropic's API does not provide a /v1/models endpoint. This validation logic will consistently return a 404 error, causing API key validation to fail even for valid keys. Consider validating the key by making a minimal request to the /v1/messages endpoint (e.g., with max_tokens=1) or simply verifying that the key is provided in the configuration.

@wzh1994 wzh1994 requested review from a team as code owners April 7, 2026 06:10
@wzh1994 wzh1994 deployed to protected April 7, 2026 06:13 — with GitHub Actions Active
@wzh1994 wzh1994 merged commit a54d655 into LazyAGI:main Apr 7, 2026
17 of 20 checks passed
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.

1 participant