[GH-ISSUE #18] 代理在使用 LangChain 的 with_structured_output 功能时,未能正确处理工具调用(Tool Calling)请求 #16

Closed
opened 2026-02-27 07:17:24 +03:00 by kerem · 3 comments
Owner

Originally created by @bosens-China on GitHub (Aug 5, 2025).
Original GitHub issue: https://github.com/justlovemaki/AIClient-2-API/issues/18

一、环境信息

  • AIClient-2-API 版本: main 分支最新版
  • LangChain 版本: ~0.2.0
  • 后端模型: gemini-1.5-pro (通过 gemini-cli-oauth 模式)
  • 调用方式: Python + LangChain (langchain-openai)

二、问题描述

当我尝试使用 LangChain 的 .with_structured_output() 方法,通过 AIClient-2-API 代理调用 Gemini 1.5 Pro 模型时,程序会抛出 pydantic.ValidationError

错误日志显示,模型返回的并非纯净的 JSON 字符串,而是包含了对话式的开场白和 Markdown 的代码块标记,例如:`Of course, here is the... ```json ... ````。

这表明 AIClient-2-API 代理在转发请求时,可能丢失了结构化输出所必需的“工具调用 (Tool Calling)”协议信息,导致 Gemini 模型将请求理解成了一个普通的聊天任务,而非严格的 JSON 生成任务。

三、复现步骤

1. 定义 Pydantic 模型 (用于结构化输出)

from pydantic import BaseModel, Field
from typing import List

class StoryVersion(BaseModel):
    level_1: str = Field(..., description="Level 1 Story")
    # ... 其他 level ...
    level_5: str = Field(..., description="Level 5 Story")

class ProcessedStory(BaseModel):
    id: str
    original_title: str
    english_title: str
    versions: StoryVersion

class BatchOutput(BaseModel):
    stories: List[ProcessedStory]```

**2. 失败的调用 (通过 AIClient-2-API 代理)**
这段代码会稳定地复现错误
```python
from langchain_openai import ChatOpenAI

# 指向 AIClient-2-API 代理
llm_proxy = ChatOpenAI(
    model="gemini-1.5-pro",
    base_url="http://localhost:3000/v1",
    api_key="YOUR_PROXY_KEY",
)

structured_llm_proxy = llm_proxy.with_structured_output(BatchOutput)

# 使用一个详细的 Prompt,要求模型输出 JSON
# (Prompt 内容见之前的讨论)
try:
    result = structured_llm_proxy.invoke("我的详细 Prompt...")
except Exception as e:
    # 这里会捕获到 Pydantic 验证错误
    print(e) ```

**3. 成功的调用 (直接连接 Google API 作为对照组)**
作为对比**完全绕过 `AIClient-2-API`**使用 `langchain-google-genai` 直接连接 Gemini **使用完全相同的 Pydantic 模型和 Prompt**程序可以完美运行
```python
from langchain_google_genai import ChatGoogleGenerativeAI

# 直接连接 Google
llm_direct = ChatGoogleGenerativeAI(
    model="gemini-1.5-pro-latest",
    google_api_key="MY_REAL_GOOGLE_KEY",
)

structured_llm_direct = llm_direct.with_structured_output(BatchOutput)

# 使用完全相同的 Prompt
result = structured_llm_direct.invoke("我的详细 Prompt...") 

# ✅ 程序成功运行,返回解析好的 Pydantic 对象
print(result)

四、期望行为

当使用 structured_llm_proxy.invoke() 时,我期望 AIClient-2-API 能够正确地将 LangChain 的工具调用请求转发给 Gemini,并返回一个可以被 BatchOutput 模型直接解析的、纯净的 JSON 字符串。

五、实际行为

程序抛出 pydantic.ValidationError,因为从代理返回的响应文本包含了非 JSON 的额外内容,导致解析失败。错误信息如下:

pydantic.ValidationError: 1 validation error for BatchOutput
  Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Of course, here is the g...n."\n    }\n  }\n]\n```', input_type=str]

六、根本原因推断

我推断问题的根本原因在于 AIClient-2-API 在转换 OpenAI 兼容的 API 请求到 Gemini 原生请求时,没有正确处理或转发‘工具调用’(Tool Calling / Function Calling)相关的参数

当 LangChain 使用 .with_structured_output() 时,它发送的 OpenAI 格式请求中会包含 toolstool_choice 字段。代理服务很可能忽略了这些字段,只提取了 messages 数组中的内容,并将其作为一个普通的聊天请求发送给了 Gemini。

因此,Gemini 从未收到“必须使用工具进行输出”的指令,导致其退回到了默认的、对话式的响应模式。

七、修复建议

建议检查代理服务中处理 /v1/chat/completions 请求的核心逻辑。需要增加对请求体中 toolstool_choice 参数的识别和转换,将其正确地映射为 Gemini API 所需的 tools 参数格式。

Originally created by @bosens-China on GitHub (Aug 5, 2025). Original GitHub issue: https://github.com/justlovemaki/AIClient-2-API/issues/18 ## **一、环境信息** * **AIClient-2-API 版本**: main 分支最新版 * **LangChain 版本**: `~0.2.0` * **后端模型**: `gemini-1.5-pro` (通过 `gemini-cli-oauth` 模式) * **调用方式**: Python + LangChain (`langchain-openai`) ## **二、问题描述** 当我尝试使用 LangChain 的 `.with_structured_output()` 方法,通过 `AIClient-2-API` 代理调用 Gemini 1.5 Pro 模型时,程序会抛出 `pydantic.ValidationError`。 错误日志显示,模型返回的并非纯净的 JSON 字符串,而是包含了对话式的开场白和 Markdown 的代码块标记,例如:`Of course, here is the... ```json ... ````。 这表明 `AIClient-2-API` 代理在转发请求时,可能丢失了结构化输出所必需的“工具调用 (Tool Calling)”协议信息,导致 Gemini 模型将请求理解成了一个普通的聊天任务,而非严格的 JSON 生成任务。 ## **三、复现步骤** **1. 定义 Pydantic 模型 (用于结构化输出)** ```python from pydantic import BaseModel, Field from typing import List class StoryVersion(BaseModel): level_1: str = Field(..., description="Level 1 Story") # ... 其他 level ... level_5: str = Field(..., description="Level 5 Story") class ProcessedStory(BaseModel): id: str original_title: str english_title: str versions: StoryVersion class BatchOutput(BaseModel): stories: List[ProcessedStory]``` **2. 失败的调用 (通过 AIClient-2-API 代理)** 这段代码会稳定地复现错误。 ```python from langchain_openai import ChatOpenAI # 指向 AIClient-2-API 代理 llm_proxy = ChatOpenAI( model="gemini-1.5-pro", base_url="http://localhost:3000/v1", api_key="YOUR_PROXY_KEY", ) structured_llm_proxy = llm_proxy.with_structured_output(BatchOutput) # 使用一个详细的 Prompt,要求模型输出 JSON # (Prompt 内容见之前的讨论) try: result = structured_llm_proxy.invoke("我的详细 Prompt...") except Exception as e: # 这里会捕获到 Pydantic 验证错误 print(e) ``` **3. 成功的调用 (直接连接 Google API 作为对照组)** 作为对比,当**完全绕过 `AIClient-2-API`**,使用 `langchain-google-genai` 直接连接 Gemini 时,**使用完全相同的 Pydantic 模型和 Prompt**,程序可以完美运行。 ```python from langchain_google_genai import ChatGoogleGenerativeAI # 直接连接 Google llm_direct = ChatGoogleGenerativeAI( model="gemini-1.5-pro-latest", google_api_key="MY_REAL_GOOGLE_KEY", ) structured_llm_direct = llm_direct.with_structured_output(BatchOutput) # 使用完全相同的 Prompt result = structured_llm_direct.invoke("我的详细 Prompt...") # ✅ 程序成功运行,返回解析好的 Pydantic 对象 print(result) ``` ## **四、期望行为** 当使用 `structured_llm_proxy.invoke()` 时,我期望 `AIClient-2-API` 能够正确地将 LangChain 的工具调用请求转发给 Gemini,并返回一个可以被 `BatchOutput` 模型直接解析的、纯净的 JSON 字符串。 ## **五、实际行为** 程序抛出 `pydantic.ValidationError`,因为从代理返回的响应文本包含了非 JSON 的额外内容,导致解析失败。错误信息如下: ``` pydantic.ValidationError: 1 validation error for BatchOutput Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='Of course, here is the g...n."\n }\n }\n]\n```', input_type=str] ``` ## **六、根本原因推断** 我推断问题的根本原因在于 `AIClient-2-API` 在转换 OpenAI 兼容的 API 请求到 Gemini 原生请求时,**没有正确处理或转发‘工具调用’(Tool Calling / Function Calling)相关的参数**。 当 LangChain 使用 `.with_structured_output()` 时,它发送的 OpenAI 格式请求中会包含 `tools` 和 `tool_choice` 字段。代理服务很可能忽略了这些字段,只提取了 `messages` 数组中的内容,并将其作为一个普通的聊天请求发送给了 Gemini。 因此,Gemini 从未收到“必须使用工具进行输出”的指令,导致其退回到了默认的、对话式的响应模式。 ## **七、修复建议** 建议检查代理服务中处理 `/v1/chat/completions` 请求的核心逻辑。需要增加对请求体中 `tools` 和 `tool_choice` 参数的识别和转换,将其正确地映射为 Gemini API 所需的 `tools` 参数格式。
kerem closed this issue 2026-02-27 07:17:24 +03:00
Author
Owner

@justlovemaki commented on GitHub (Aug 5, 2025):

在源码convert.js的toGeminiRequestFromOpenAI中是有工具调用的转换的。

我推测是使用的模型输出不稳定,提示词可以再优化一下,增加禁止行为,让输出更确定。也可能是gemini-1.5不支持工具调用。

另外,你可以增加日志,把转换前和转换后的request作为文件传上来,这样更有利于排查问题。

<!-- gh-comment-id:3154498950 --> @justlovemaki commented on GitHub (Aug 5, 2025): 在源码convert.js的toGeminiRequestFromOpenAI中是有工具调用的转换的。 我推测是使用的模型输出不稳定,提示词可以再优化一下,增加禁止行为,让输出更确定。也可能是gemini-1.5不支持工具调用。 另外,你可以增加日志,把转换前和转换后的request作为文件传上来,这样更有利于排查问题。
Author
Owner

@bosens-China commented on GitHub (Aug 5, 2025):

在源码convert.js的toGeminiRequestFromOpenAI中是有工具调用的转换的。

我推测是使用的模型输出不稳定,提示词可以再优化一下,增加禁止行为,让输出更确定。也可能是gemini-1.5不支持工具调用。

另外,你可以增加日志,把转换前和转换后的request作为文件传上来,这样更有利于排查问题。

切换到2.5pro也是同样的问题,在调试的时候发现有一些非结构的输出,例如“用户要求...”这个推测才是导致无法结构化输出的原因,你在使用的过程中有相关的调用成功示例吗?
我使用了gemni的api密钥,发现是可以的,所以提示词的问题可能关系不大。

<!-- gh-comment-id:3155296475 --> @bosens-China commented on GitHub (Aug 5, 2025): > 在源码convert.js的toGeminiRequestFromOpenAI中是有工具调用的转换的。 > > 我推测是使用的模型输出不稳定,提示词可以再优化一下,增加禁止行为,让输出更确定。也可能是gemini-1.5不支持工具调用。 > > 另外,你可以增加日志,把转换前和转换后的request作为文件传上来,这样更有利于排查问题。 切换到2.5pro也是同样的问题,在调试的时候发现有一些非结构的输出,例如“用户要求...”这个推测才是导致无法结构化输出的原因,你在使用的过程中有相关的调用成功示例吗? 我使用了gemni的api密钥,发现是可以的,所以提示词的问题可能关系不大。
Author
Owner

@justlovemaki commented on GitHub (Aug 5, 2025):

所以还是得看你的请求json,把转换前和转换后的request作为文件传上来。
请求里传了工具调用with_structured_output,返回的不是json,就可以排除工具调用没传得问题, 可能是模型不支持调用工具。
“例如“用户要求...”这个推测才是导致无法结构化输出的原因”,应该是开了think,把think标签得内容移除,或者关闭think试一下。

<!-- gh-comment-id:3155814221 --> @justlovemaki commented on GitHub (Aug 5, 2025): 所以还是得看你的请求json,把转换前和转换后的request作为文件传上来。 请求里传了工具调用with_structured_output,返回的不是json,就可以排除工具调用没传得问题, 可能是模型不支持调用工具。 “例如“用户要求...”这个推测才是导致无法结构化输出的原因”,应该是开了think,把think标签得内容移除,或者关闭think试一下。
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/AIClient-2-API-justlovemaki#16
No description provided.