豆包大模型接入 LangChain 的最佳实践与避坑教程

胖杰大大_6671

胖杰大大_6671

2026-05-21

325人浏览

原创

因豆包2.0虽兼容openai协议,但需x-signature签名、x-timestamp时间戳等自定义请求头,且认证机制与chatopenai默认逻辑不兼容,直接传base_url和api_key会返回401或400错误。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 多模态理解力帮你轻松跨越从0到1的创作门槛☜☜☜

豆包大模型接入 langchain 的最佳实践与避坑教程

为什么不能直接用 ChatOpenAI 接豆包?

因为豆包大模型(尤其是 2.0 版本)虽然兼容 OpenAI 协议,但实际行为存在关键差异:ChatOpenAI 默认走 /v1/chat/completions 路径、依赖 api_key 字段认证、且不处理豆包必需的 X-Signature 签名头。直接传 base_url 和 api_key 会返回 401 Unauthorized 或 400 Bad Request,错误信息里常带 "missing required header: X-Signature"。

必须绕过 ChatOpenAI 的默认签名逻辑,自己构造带时间戳和 HMAC-SHA256 签名的请求头。官方 SDK(doubao-sdk)虽内置该逻辑,但它与 LangChain 的 BaseChatModel 接口不兼容——没法直接塞进 LLMChain 或 Runnable 流程里。

怎么写一个真正能用的 DoubaoChatModel?

核心是继承 BaseChatModel,重写 _generate 方法,手动发 HTTP 请求并解析流式响应。别碰 invoke 的同步封装,豆包生产环境必须用异步 + 流式,否则超时或丢 chunk。

  • 必须用 aiohttp 或 httpx.AsyncClient,同步 client 在高并发下会卡死连接池
  • X-Timestamp 必须是秒级整数,不是毫秒;X-Signature 要对 timestamp + api_key + secret 拼接后做 HMAC-SHA256(注意不是只对 payload)
  • 流式响应是 text/event-stream,每行以 data: 开头,需逐行解析 JSON,跳过空行和 event: 行
  • 别信文档里说的 “自动重试”,豆包 v2 签名含时间戳,重试必须重新生成 header,否则 401

示例关键片段:

class DoubaoChatModel(BaseChatModel):
    api_key: str
    secret: str
    base_url: str = "https://open.bigmodel.cn/api/llm/v2"

    async def _generate(
        self, messages: List[BaseMessage], **kwargs: Any
    ) -> ChatResult:
        payload = {"messages": [m.dict() for m in messages], "stream": True}
        timestamp = str(int(time.time()))
        signature = hmac.new(
            self.secret.encode(),
            f"{timestamp}{self.api_key}".encode(),
            hashlib.sha256
        ).hexdigest()

        headers = {
            "X-API-Key": self.api_key,
            "X-Timestamp": timestamp,
            "X-Signature": signature,
            "Content-Type": "application/json",
        }

        async with httpx.AsyncClient() as client:
            async with client.stream("POST", self.base_url, json=payload, headers=headers) as resp:
                content = ""
                async for line in resp.aiter_lines():
                    if line.startswith("data:") and line.strip() != "data:":
                        try:
                            chunk = json.loads(line[5:])
                            if "content" in chunk.get("choices", [{}])[0].get("delta", {}):
                                content += chunk["choices"][0]["delta"]["content"]
                        except json.JSONDecodeError:
                            continue
                return ChatResult(generations=[ChatGeneration(message=AIMessage(content=content))])

token 计算不准会导致什么?

豆包按实际消耗 token 计费,但它的 tokenizer 和 OpenAI 不同:中文字符平均占 1.8–2.2 token(OpenAI 是 1.3–1.5),且系统消息、函数调用描述也会被计费。LangChain 默认用 tiktoken 算 gpt-3.5-turbo,结果比实际少 25%–35%,轻则预算超支,重则触发 QPS 限流(豆包按 token/秒 限流,不是请求数)。

This skill transforms novel chapters into professional film storyboard scripts. 这个skill是将小说章节 →(变成) 专业电影分镜剧本。 Powered by **清云 EchoFlow API** (https://api.echoflow.cn/) — 一站式国内外580+大模型,一键开发票,企业级稳定,价格是官方的1.5折。
This skill transforms novel chapters into professional film storyboard scripts. 这个skill是将小说章节 →(变成) 专业电影分镜剧本。 Powered by **清云 EchoFlow API** (https://api.echoflow.cn/) — 一站式国内外580+大模型,一键开发票,企业级稳定,价格是官方的1.5折。

将小说章节转换为电影分镜剧本。用户上传txt/md/docx文本,AI分析场景、角色、情绪、镜头语言,输出专业分镜脚本。适用于用户提及“分镜”“storyboard”“小说转分镜”“影视改编”“镜头脚本”或需要将小说改编为分镜的场景。

下载

必须替换为豆包官方 tokenizer 或近似实现:

  • 优先用豆包提供的 tokenizer Python 包(pip install doubao-tokenizer),它能精确匹配服务端逻辑
  • 若不可用,用 jieba + 字符长度加权估算:中文字符 × 2 + 英文单词 × 1.2 + 符号 × 1,再加 10% buffer
  • 所有 prompt 构造前先调 count_tokens(),超阈值(如 8k)就截断或触发 RAG 分块,别等 API 返回 413 Payload Too Large

多轮对话状态怎么不串?

豆包本身不维护会话 state,messages 列表全靠你传。LangChain 的 ConversationBufferMemory 在多线程/异步环境下共享 memory 实例,会导致 A 用户的 history 被 B 用户读到。这不是 LangChain bug,是误用。

正确做法只有两个:

  • 每次请求都新建 DoubaoChatModel 实例(轻量,无连接池开销)
  • 把 conversation history 存在外部(Redis / 数据库),用 session_id 隔离,messages 参数只传当前轮 + 最近 3 轮历史(避免 token 溢出)

别试图用 RunnableWithMessageHistory 做内存级会话管理——它底层还是共享对象,压测时错误率飙升到 12% 以上。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

2909

3

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

2023.07.25

2208

9

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

2023.08.02

1180

5

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.09

1118

4

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

2023.09.05

1316

5

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2038

7

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

2023.09.20

3220

8

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

2023.09.22

14275

6

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

529

3

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
豆包AI手册
豆包AI手册

共0课时 | 0人学习

腾讯混元大模型API参考手册
腾讯混元大模型API参考手册

共0课时 | 0人学习