新手教程:解决 Instructor 常见错误(多工具调用、字段校验、API 兼容性等)

当你在使用 instructor 库(一个让大模型输出结构化数据的 Python 库)配合大模型时,可能会遇到各种错误。本教程会用大白话告诉你:为什么会出这些错,以及 每种情况对应的解决方案

目录

  1. 错误 1:Instructor does not support multiple tool calls
  2. 错误 2:Pydantic 对象没有某个字段
  3. 错误 3:API 拒绝 messages 内容
  4. 错误 4:多模型额度切换时 Pydantic 字段污染
  5. 总结:避坑清单

错误 1:Instructor does not support multiple tool calls

1.1 错误出现的场景

典型的代码长这样:

import instructor
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",   # 你的本地模型服务地址
    api_key="ollama",
)
client = instructor.from_openai(client)      # 没有指定模式

response = client.chat.completions.create(
    model="llama3.2",
    response_model=YourPydanticModel,       # 期望得到结构化输出
    messages=[...],
)

运行后,控制台就报出了那个错误。

1.2 为什么会发生这个错误?

instructor 默认使用 Tool Calling(函数调用)模式。这个模式要求模型严格按照 OpenAI 的函数调用规范返回结果。但是:

  • 许多本地模型(如 Llama 3、Yi-Coder、Qwen 等)对函数调用的支持不完善。
  • 即使模型支持,有时模型会尝试返回多个工具调用,而 instructor 默认无法处理多个调用,所以就会报错并提示你改用 List[Model]

实际上,你完全不需要改用 List[Model]。更简单的做法是: instructor 换一种工作方式,不再依赖函数调用,而是直接让模型输出纯 JSON。

1.3 解决方案总览

方案 核心思路 适用场景
使用 Mode.MD_JSON 从 Markdown 代码块中提取 JSON 首选,几乎兼容所有模型
使用 Mode.JSON 要求模型直接输出纯 JSON API 支持原生 JSON 模式时更规范

1.4 方案一:使用 MD_JSON 模式(推荐新手首选)

什么是 MD_JSON?

很多模型喜欢把输出的 JSON 放在 Markdown 代码块里,例如:

```json
{"name": "张三", "age": 25}
```

Mode.MD_JSON 就是让 instructor 自动识别并提取这个代码块里的 JSON,而不管外面有没有额外文字。

代码修改(只需改一行)

修改前:

client = instructor.from_openai(client)

修改后:

client = instructor.from_openai(client, mode=instructor.Mode.MD_JSON)

完整示例:

import instructor
from openai import OpenAI
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama",
)
client = instructor.from_openai(client, mode=instructor.Mode.MD_JSON)

response = client.chat.completions.create(
    model="llama3.2",
    response_model=Person,
    messages=[{"role": "user", "content": "张三,25岁"}],
)

print(response.name)  # 张三
print(response.age)   # 25

4.3 为什么 MD_JSON 最稳定?

  • 即使模型在 JSON 前后加了说明文字,也能正常工作。
  • 不依赖模型的函数调用能力,兼容性最好。
  • 因此它是新手遇到错误时的第一选择

1.5 方案二:使用 JSON 模式(更严格,适合高级用户)

什么是 JSON 模式?

Mode.JSON 强制模型必须输出一个纯 JSON 字符串,不能有任何额外文本(包括代码块标记)。它通常需要 API 提供原生支持(如 OpenAI 的 response_format={"type": "json_object"})。

代码修改(也是改一行)
client = instructor.from_openai(client, mode=instructor.Mode.JSON)

完整示例:

client = instructor.from_openai(client, mode=instructor.Mode.JSON)

注意:使用 Mode.JSON 时,建议在系统提示词或用户消息中明确要求只输出 JSON,例如:

messages=[
    {"role": "system", "content": "You are a helpful assistant. Always output valid JSON without any extra text."},
    {"role": "user", "content": "张三,25岁"}
]
什么时候用 JSON 模式?
  • 你使用的是官方 OpenAI、Gemini 等云端 API,并且它们支持 response_format
  • 你对输出格式有严格的要求,不希望有任何额外的自然语言。
  • 如果本地模型也能稳定输出纯 JSON(经过测试),也可以使用。

1.6 两种模式对比一览表

特性 Mode.MD_JSON Mode.JSON
提取方式 正则提取代码块内的 JSON 直接解析 API 返回的纯文本
对额外文字 容错,会忽略代码块外的内容 不兼容,模型必须只输出 JSON
API 要求 无特殊要求,所有模型通用 需 API 支持 response_format 或能稳定输出纯 JSON
推荐指数 ⭐⭐⭐⭐⭐(新手首选) ⭐⭐⭐(进阶使用)

1.7 额外优化建议:让模型乖乖输出 JSON

即使你使用了上述模式,有时模型仍然会“跑偏”,比如在 JSON 前加上“Sure, here is the JSON:”。你可以通过以下方式进一步提高成功率:

在用户消息中明确要求

user_content = """
请根据以下信息输出 JSON:
姓名:张三,年龄:25

重要:你的回复必须是一个合法的 JSON 对象,不要包含任何其他文字。
"""

使用 output_instructions 参数(如果模型 API 支持)

response = client.chat.completions.create(
    ...,
    extra_body={
        "output_instructions": ["Your response must be a valid JSON object."]
    }
)

1.8 常见问题 FAQ

Q1:修改模式后,我的 response_model 还能自动验证吗?
A:可以。instructor 的 JSON 和 MD_JSON 模式都会自动解析并验证 Pydantic 模型,你不需要修改其他代码。

Q2:我用了 MD_JSON 还是报错怎么办?
A:极少数情况模型连 JSON 都不输出。你可以打印 response.choices[0].message.content 查看原始输出,然后根据实际内容调整提示词。通常添加“只输出 JSON,不要有任何解释”就能解决。

Q3:我需要把项目中所有 instructor.from_openai 都改一遍吗?
A:是的,建议统一修改,以保持行为一致。

1.9 小结

当你看到 Instructor does not support multiple tool calls 这个错误时:

  1. 不要慌,这不是你代码逻辑的错误,而是 instructor 默认模式与本地模型不太合拍。
  2. 立即尝试:将 instructor.from_openai(client) 改为 instructor.from_openai(client, mode=instructor.Mode.MD_JSON)
  3. 如果还想更严格,可以尝试 Mode.JSON,但记得在提示词中强调"只输出 JSON"。
  4. 最后,享受 instructor 带来的结构化数据提取便利吧!

现在你可以放心地让本地模型输出 Pydantic 对象,而不再被那个烦人的错误困扰了。


错误 2:Pydantic 对象没有某个字段(object has no field “xxx”)

2.1 错误信息

ValidationError: "TrafficAccidentAnalysis" object has no field "used_model"

或者在响应时出现:

"TrafficAccidentAnalysis" object has no field "used_model"

2.2 错误场景

你希望给 instructor 返回的 Pydantic 对象"附加"一些额外信息(比如记录用了哪个模型),于是写了类似这样的代码:

response = client.chat.completions.create(
    model=current_model,
    response_model=TrafficAccidentAnalysis,    # 严格按这个模型校验
    messages=[...],
)
response.analysis_id = analysis_id            # 这两个字段 Pydantic 里没有!
response.used_model = current_model           # 报错就出在这里

2.3 为什么会发生这个错误?

Pydantic 模型在 model_config = ConfigDict(extra="forbid")(或默认行为)下,禁止给实例添加未声明的字段。一旦你赋值一个未定义的字段,Pydantic 就会抛 ValidationError

2.4 解决方案:把"额外信息"作为返回值的一部分

错误示例

response = client.chat.completions.create(...)
response.used_model = current_model    # ❌ 报错

正确示例 1:把额外信息放到 Pydantic 模型里

class TrafficAccidentAnalysis(BaseModel):
    analysis_id: str
    used_model: str = "unknown"        # 显式声明
    # ... 其他字段

response = client.chat.completions.create(
    response_model=TrafficAccidentAnalysis,
    ...
)
response.used_model = current_model    # ✅ OK

正确示例 2:用元组返回(推荐)

def analyze(video_path, current_model):
    response = client.chat.completions.create(...)
    return response, current_model     # ✅ 模型名作为局部变量传递

result, used_model = analyze(video_path, "qwen")

正确示例 3:放到外部 dict

result = {
    "analysis": response,
    "used_model": current_model,
}

2.5 避坑口诀

Pydantic 实例不能"加字段",要么声明,要么用元组/dict 携带。


错误 3:API 拒绝 messages 内容(Unexpected item type in content)

3.1 错误信息

openai.BadRequestError: Error code: 400 - {'error': {'message':
'<400> InternalError.Algo.InvalidParameter: The provided messages input is invalid.
The error info is [Unexpected item type in content.].', 'type': 'invalid_request_error'}}

3.2 错误场景

你用 instructor 调用了某些云端 API(例如阿里百炼 DashScope 的 OpenAI 兼容模式),模型明明支持函数调用,但请求却报 400 错。

3.3 为什么会发生这个错误?

instructor 默认的 Tool Calling 模式会在 messages 中插入 tool_calls 类型的内容项。但某些 API(特别是第三方 OpenAI 兼容 API)对 content 字段类型校验非常严格,会拒绝 tool_calls 类型,于是返回 400。

3.4 解决方案

方案 A:切换到 Mode.MD_JSON(推荐)

import instructor
client = instructor.from_openai(client, mode=instructor.Mode.MD_JSON)

这种方式完全不走 tool_calls,content 全部是普通文本,兼容性最好。

方案 B:使用 instructor.Mode.JSON + 强提示词

client = instructor.from_openai(client, mode=instructor.Mode.JSON)
# 同时在 system prompt 中加:
# "请只输出 JSON,不要包含任何其他文字。"

方案 C:自定义重试,跳过这种错误

try:
    response = client.chat.completions.create(...)
except BadRequestError as e:
    if "Unexpected item type" in str(e):
        # 切换到下一个模型
        ...

3.5 避坑口诀

遇到 “Unexpected item type” 99% 是 instructor 走 tool_calls 模式的问题,换成 MD_JSON 即可。


错误 4:多模型额度切换时 Pydantic 字段污染

4.1 错误信息

当你在做"多模型自动 fallback"(A 模型额度用完切 B 模型)时,可能会出现以下两类问题:

  1. object has no field "xxx"(Pydantic 字段污染)
  2. 模型切换时,递归调用导致 time.sleep 失败或 KeyboardInterrupt

4.2 错误场景

def analyze_with_fallback(video_path, model_index=0):
    current_model = MODELS[model_index]
    try:
        response = client.chat.completions.create(
            model=current_model,
            response_model=TrafficAccidentAnalysis,
            ...
        )
        response.used_model = current_model   # ❌ 字段污染
        return response
    except Exception as e:
        time.sleep(1)
        return analyze_with_fallback(video_path, model_index + 1)  # 递归

4.3 为什么会发生这个错误?

  • 字段污染:参考错误 2,给 Pydantic 实例加未声明字段。
  • 递归陷阱:如果 time.sleep 在 except 块里,每次重试都会等;但如果用户在等待时按 Ctrl+C,会因为递归太深导致 KeyboardInterrupt 难以处理。

4.4 解决方案

def analyze_with_fallback(video_path, model_index=0):
    """返回 (response, used_model) 元组"""
    if model_index >= len(MODELS):
        return None, None

    current_model = MODELS[model_index]
    try:
        response = client.chat.completions.create(
            model=current_model,
            response_model=TrafficAccidentAnalysis,
            ...
        )
        return response, current_model         # ✅ 用元组返回
    except Exception as e:
        time.sleep(2)
        return analyze_with_fallback(video_path, model_index + 1)

# 调用
result, used_model = analyze_with_fallback(video_path)

4.5 进阶优化:循环代替递归

如果模型特别多,递归可能太深,可以改成循环:

def analyze_with_fallback(video_path):
    for model_index, current_model in enumerate(MODELS):
        try:
            response = client.chat.completions.create(
                model=current_model,
                response_model=TrafficAccidentAnalysis,
                ...
            )
            return response, current_model
        except Exception as e:
            print(f"模型 {current_model} 失败: {e}")
            time.sleep(2)
            continue
    return None, None

4.6 避坑口诀

多模型 fallback 用元组返回模型名;模型很多时用循环代替递归。


总结:避坑清单

错误类型 错误信息关键词 一句话修复
错误 1 multiple tool calls 改为 mode=instructor.Mode.MD_JSON
错误 2 object has no field Pydantic 不能加字段,用元组/dict 携带
错误 3 Unexpected item type 切换到 MD_JSON 模式
错误 4 多模型 fallback 字段污染 用元组返回,或循环代替递归

终极口诀

  1. 遇到 instructor 报错,先看错误信息的关键词,对应到上面 4 类问题。
  2. MD_JSON 是万能兜底模式,80% 的兼容性问题用它就能解决。
  3. Pydantic 模型字段必须显式声明,不能"加塞"。
  4. 多模型 fallback 用元组或循环,别让 Pydantic 实例承担额外数据。
  5. 不要忘了捕获异常后 time.sleep(1~2),避免被限流。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐