Skip to content

visionki/fal2claude

Repository files navigation

Fal2Claude - Claude Messages API 适配器

将 Fal.ai 的 any-llm 端点适配为 Claude Messages API 格式,通过 Prompt 工程为不支持原生工具调用的模型提供完整的 function calling 能力。

Claude Code 适配效果

背景与挑战

Fal.ai 的 any-llm 端点提供了统一的 LLM 访问接口,但存在以下限制:

  • 仅支持 2 个参数promptsystem_prompt(纯文本)
  • 不支持原生 tools:无 function calling API
  • 参数长度限制:标准端点每个参数最多 5000 字符(最近新增的企业端点无限制)

而 Claude Messages API 提供了:

  • ✅ 结构化的 messages 数组
  • ✅ 原生的 tools 定义和调用
  • ✅ 多轮对话上下文管理

本项目的目标:在不修改上游 API 的前提下,通过 Prompt 工程实现完整的工具调用能力。

核心特性

  • Prompt 工程实现工具调用 - 为只有 prompt/system_prompt 的上游提供 function calling
  • 堆栈截断防幻觉 - 确保工具调用后模型停止输出,等待真实结果
  • 智能端点切换 - 根据 prompt 长度自动选择标准或企业端点
  • 模型名称映射 - 灵活映射 Claude 模型名到实际 Fal 模型
  • 完整 Claude API 兼容 - 流式/非流式、多轮对话、工具调用

技术方案

1. 核心思路:XML 标签 + 约定协议

由于上游只接受纯文本 system_prompt,我们将工具定义、调用规则、对话历史全部编码为 XML 结构:

输入侧(注入 system_prompt)

<system> 用户的原始 system prompt </system> <conversation_history> <user>之前的用户消息</user> <assistant>之前的助手回复</assistant> <tool_result call_id="xxx">工具执行结果</tool_result> </conversation_history> <tools> <tool name="get_weather"> <description>获取天气信息</description> <parameters> <json_schema>{"type":"object","properties":{...}}</json_schema> </parameters> </tool> </tools> <tool_call_rules> <rule>只能调用本轮 <tools> 中列出的工具</rule> <rule>输出 <tool_calls>...</tool_calls> 后立即停止,不得继续输出</rule> <rule>禁止模拟工具执行结果</rule> <rule>线性依赖:一次只调用一个工具,等待真实结果后再继续</rule> <rule>并行调用:无依赖时可在同一 <tool_calls> 中列出多个</rule> </tool_call_rules> <output_format> 仅回答内容(无需工具时) 或 <tool_calls> <tool_call name="..." call_id="..."> <arguments>{...JSON...}</arguments> </tool_call> </tool_calls> </output_format> 

输出侧(模型响应)

可选的文本回复 <tool_calls> <tool_call name="get_weather" call_id="call_xxx"> <arguments>{"city": "北京"}</arguments> </tool_call> </tool_calls> 

2. 关键设计:堆栈截断防幻觉

问题:模型可能在输出 </tool_calls> 后继续"幻觉",例如:

  • 自行编造工具执行结果
  • 继续模拟下一轮对话
  • 输出额外的解释文字

解决方案:栈式 XML 解析器 + 第一闭合即停止

解析流程: 1. 遇到 <tool_calls> → 入栈 2. 遇到 <tool_call> → 入栈,记录 name 和 call_id 3. 遇到 <arguments> → 入栈,开始收集内容 4. 遇到 </arguments> → 出栈,解析 JSON 5. 遇到 </tool_call> → 出栈,保存工具调用 6. 遇到 </tool_calls> → 出栈,🔒 立即停止解析 

关键点:第 6 步遇到第一个 </tool_calls> 闭合标签时,立即 break,后续内容全部丢弃。

效果

  • ✅ 模型无法在工具调用后继续输出
  • ✅ 必须等待下一轮注入 <tool_result> 才能继续
  • ✅ 强制模型遵循"调用 → 等待 → 结果 → 继续"的流程

3. 智能补全与容错

模型输出可能不规范,需要容错处理:

场景 1:忘记闭合 </arguments>

<tool_call name="get_weather"> <arguments>{"city": "北京"} </tool_call> ← 直接跳到这里了 

处理:检测到 </tool_call> 时,如果栈顶还是 arguments,自动闭合并解析

场景 2:JSON 带尾部逗号

<arguments>{"city": "北京",}</arguments> 

处理:正则替换 ,}},]]

场景 3:缺少 input

<tool_call name="get_weather" call_id="xxx"> </tool_call> 

处理:自动补充空对象 {}

4. 历史对话的编码

多轮对话需要将历史压缩到 system_prompt 中:

编码规则

  • 最后一条消息 → 放入 prompt 参数(用户当前问题)
  • 其余历史 → 编码为 XML 放入 system_prompt

工具结果的处理

用户: "北京天气" 助手: <tool_calls>...</tool_calls> ← 工具调用 用户: [tool_result: {"temp": 15}] ← 工具结果 编码为: <conversation_history> <user>北京天气</user> <assistant_tool_calls> <tool_call name="get_weather" call_id="xxx">...</tool_call> </assistant_tool_calls> <tool_result call_id="xxx">{"temp": 15}</tool_result> </conversation_history> 当前消息: (助手基于工具结果继续回答) 

5. 端点选择策略

Fal.ai 提供两个端点:

  • 标准端点 (fal-ai/any-llm):每个参数 ≤5000 字符
  • 企业端点 (fal-ai/any-llm/enterprise):最近新增,无长度限制,成本更高

选择逻辑

if (system_prompt.length > 5000 || prompt.length > 5000) { 使用企业端点 } else { 使用标准端点 } 

优化思路

  • 工具定义在每轮重复,占用较多空间
  • 历史对话会累积增长
  • 超过 5000 字符时自动切换,对用户透明

6. 伪流式输出

上游限制:只有非流式 API (fal.subscribe())

下游需求:客户端可能要求 SSE 流式输出

解决方案

  1. 上游统一使用非流式调用,获取完整响应
  2. 解析完整内容,提取文本和工具调用
  3. 下游需要流式时,将内容分块输出:
    • 文本按 15 字符/块输出,间隔 10ms(模拟打字)
    • 工具调用的 JSON 按 20 字符/块输出,间隔 8ms
    • 发送标准 SSE 事件:message_startcontent_block_deltamessage_stop

优势

  • 简化实现,无需复杂的流式状态机
  • 上游解析只需处理完整内容,无需处理截断

快速开始

Docker Compose(推荐)

# 克隆项目 git clone https://github.com/your-username/fal2claude.git cd fal2claude # 启动服务 docker-compose up -d # 查看日志 docker-compose logs -f

配置说明

docker-compose.yml 中配置模型映射:

environment: - MODEL_MAPPING={"claude-sonnet-4-5-20250929":"anthropic/claude-3.7-sonnet"}

API 使用

基本对话

curl http://localhost:8080/v1/messages \ -H "x-api-key: YOUR_FAL_KEY" \ -H "Content-Type: application/json" \ -d '{  "model": "claude-sonnet-4-5-20250929",  "messages": [{"role": "user", "content": "你好"}],  "max_tokens": 1024  }'

工具调用

curl http://localhost:8080/v1/messages \ -H "x-api-key: YOUR_FAL_KEY" \ -H "Content-Type: application/json" \ -d '{  "model": "google/gemini-2.5-flash",  "messages": [{"role": "user", "content": "北京现在几点?"}],  "tools": [{  "name": "get_current_time",  "description": "获取指定城市的当前时间",  "input_schema": {  "type": "object",  "properties": {  "city": {"type": "string", "description": "城市名称"}  },  "required": ["city"]  }  }]  }'

响应

{ "id": "msg_xxx", "type": "message", "role": "assistant", "content": [ { "type": "tool_use", "id": "toolu_xxx", "name": "get_current_time", "input": {"city": "北京"} } ], "stop_reason": "tool_use" }

工具结果继续对话

curl http://localhost:8080/v1/messages \ -H "x-api-key: YOUR_FAL_KEY" \ -H "Content-Type: application/json" \ -d '{  "model": "google/gemini-2.5-flash",  "messages": [  {"role": "user", "content": "北京现在几点?"},  {"role": "assistant", "content": [  {"type": "tool_use", "id": "toolu_xxx", "name": "get_current_time", "input": {"city": "北京"}}  ]},  {"role": "user", "content": [  {"type": "tool_result", "tool_use_id": "toolu_xxx", "content": "14:30"}  ]}  ],  "tools": [...]  }'

架构流程

请求转换 (Claude → Fal) ├─ 提取 messages、system、tools ├─ 构建 system_prompt │ ├─ 原始 system │ ├─ 对话历史 (XML) │ ├─ 工具定义 (XML) │ ├─ 调用规则 (XML) │ └─ 输出格式 (XML) └─ 提取最后一条消息 → prompt 上游调用 ├─ 选择端点 (标准/企业) ├─ fal.subscribe(endpoint, { │ prompt: "...", │ system_prompt: "...", │ model: "..." │ }) └─ 获取完整文本响应 响应解析 (Fal → Claude) ├─ 栈式 XML 解析 │ ├─ 识别 <tool_calls> │ ├─ 提取工具名、call_id、arguments │ └─ 🔒 遇到 </tool_calls> 立即停止 ├─ 分离文本和工具调用 └─ 构建 Claude API 响应 下游输出 ├─ 非流式: 直接返回 JSON └─ 流式: 分块输出 SSE 事件 

环境变量

变量 说明 默认值
PORT 服务端口 8080
MODEL_MAPPING 模型映射 JSON {}

注意:API Key 从请求头 x-api-keyAuthorization: Bearer <key> 中提取。

日志示例

[req-abc123] 📥 收到请求 [req-abc123] 请求模型: google/gemini-2.5-flash [req-abc123] Messages: 1 条 | Tools: 1 个 | Stream: 否 [req-abc123] 🔧 Prompt 转换完成 [req-abc123] system_prompt 长度: 487 字符 [req-abc123] message_prompt 长度: 18 字符 [req-abc123] 🎯 端点选择: fal-ai/any-llm [req-abc123] 原因: system=487, message=18 [req-abc123] 🚀 调用上游 fal.ai... [req-abc123] ✅ 上游返回成功 [req-abc123] 输出长度: 156 字符 [req-abc123] 🔍 解析结果 [req-abc123] 文本内容: 0 字符 [req-abc123] 工具调用: 1 个 [req-abc123] 工具调用详情: [{"id":"toolu_xxx","name":"get_current_time","input":{"city":"北京"}}] [req-abc123] 📤 返回非流式响应 [req-abc123] ✅ 完成 

核心价值

本项目展示了 Prompt 工程在 API 适配中的应用:

  1. 突破 API 限制:将只有 2 个文本参数的简单接口,扩展为支持结构化工具调用的复杂协议
  2. 防幻觉机制:通过堆栈截断确保模型严格遵循工具调用流程,不会自行编造结果
  3. 协议转换:在 Claude Messages API 和 Fal.ai 的文本接口之间建立双向映射

适用场景

  • 为不支持原生 function calling 的 LLM 提供工具调用能力
  • 统一多个上游 API 为标准的 Claude 格式
  • 在保持客户端兼容性的前提下切换后端模型

许可证

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors