【Bug已解决】HuggingFaceEndpointEmbeddings missing bill_to / additional_headers parameter
一、现象长什么样
HuggingFaceEndpointEmbeddings(LangChain 里对接 Hugging Face 推理端点 / TEI 的嵌入封装)在初始化时只暴露了model、endpoint_url、huggingfacehub_api_token、task等少数参数。但 Hugging Face 的 endpoints 实际还支持两个很有用的东西:
bill_to:指定这次调用计费到哪个组织/账户(企业里多个团队共用一个 endpoint 时,需要把用量归因到具体 team)。additional_headers:透传额外的 HTTP 头,比如自定义的追踪头、合规所需要的 header。
当你想传这两个参数时,会发现HuggingFaceEndpointEmbeddings根本没有对应字段,要么被 pydantic 拒绝(extra_forbidden),要么只能 monkey-patch 内部requests调用。对于需要按团队计费、或需要注入追踪 header 的企业场景,这直接卡死。
二、背景
Hugging Face 的推理端点(包括自托管的 Text Embedding Inference,TEI)在请求体或请求头里接受额外字段。bill_to通常作为请求的一个业务参数(或 header),用于用量计费归属;additional_headers则是把任意自定义 header 透传到后端,常见于服务网格、链路追踪(traceparent)、合规审计。
LangChain 的HuggingFaceEndpointEmbeddings在_embed_texts里直接用requests.post(url, json=payload, headers=...),但payload和headers都是写死的,没有把"用户想额外传的字段"纳入。pydantic model 默认extra='forbid',用户加bill_to=会直接校验失败。
三、根因
根因两点:
- 字段未建模:
bill_to与additional_headers没有在 dataclass/pydantic model 里声明,用户无法合法传入。 - 透传被切断:
_embed_texts组装payload和headers时,没有把这两个值注入到请求里,即便用户通过model_kwargs之类的后门塞进去,也未必被正确透传。
本质:封装层只覆盖了"最小可用"参数,没把底层 endpoint 支持的"计费/透传"能力暴露给调用方,而企业场景恰恰离不开这两样。
四、最小可运行复现
下面演示问题:传bill_to直接被 pydantic 拒。
from langchain_huggingface import HuggingFaceEndpointEmbeddings emb = HuggingFaceEndpointEmbeddings( model="BAAI/bge-large-en-v1.5", endpoint_url="https://my-endpoint.endpoints.huggingface.cloud", huggingfacehub_api_token="hf_xxx", bill_to="team-a", # 报错:字段不存在 additional_headers={"X-Trace": "abc"}, )报错类似:
ValidationError: 1 validation error for HuggingFaceEndpointEmbeddings bill_to extra fields not permitted (type=value_error.extra)五、解决方案(第一层:最小直接修复)
最小修法:在 model 里加两个可选字段,并在_embed_texts里把它们注入请求。
from typing import Dict, Optional class HuggingFaceEndpointEmbeddingsPatch: bill_to: Optional[str] = None additional_headers: Dict[str, str] = {} def _embed_texts(self, texts): payload = { "inputs": texts, "task": self.task, } if self.bill_to: payload["bill_to"] = self.bill_to # 注入计费归属 headers = { "Authorization": f"Bearer {self.huggingfacehub_api_token}", "Content-Type": "application/json", } headers.update(self.additional_headers) # 透传自定义头 import requests resp = requests.post(self.endpoint_url, json=payload, headers=headers, timeout=30) return resp.json()这一层让两个参数可用,且不影响既有调用。
六、解决方案(第二层:结构化改进)
把"endpoint 额外参数"固化成策略对象,作为单一事实来源,明确哪些参数走 body、哪些走 header。
from dataclasses import dataclass, field from typing import Dict, Optional @dataclass(frozen=True) class LangChainHfEndpointEmbeddingsPolicy: """HuggingFaceEndpointEmbeddings 透传策略的单一事实来源。""" bill_to: Optional[str] = None additional_headers: Dict[str, str] = field(default_factory=dict) bill_to_goes_to_body: bool = True merge_headers: bool = True def build_payload(self, base: dict) -> dict: if self.bill_to_goes_to_body and self.bill_to: base = {**base, "bill_to": self.bill_to} return base def build_headers(self, base: dict) -> dict: if self.merge_headers: return {**base, **self.additional_headers} return base def validate(self) -> None: if self.bill_to and not self.bill_to_goes_to_body: raise AssertionError("bill_to must go to request body")这样调用方只需构造LangChainHfEndpointEmbeddingsPolicy,embedding 类据此注入,职责清晰、易测试。
七、解决方案(第三层:断言 / CI 守护)
用 pytest 锁死透传行为:
import pytest from policy import LangChainHfEndpointEmbeddingsPolicy as P def test_bill_to_in_body(): p = P(bill_to="team-a") payload = p.build_payload({"inputs": ["x"]}) assert payload["bill_to"] == "team-a" def test_additional_headers_merged(): p = P(additional_headers={"X-Trace": "t1"}) headers = p.build_headers({"Authorization": "Bearer x"}) assert headers["X-Trace"] == "t1" assert headers["Authorization"] == "Bearer x" def test_no_bill_to_no_field(): p = P() payload = p.build_payload({"inputs": ["x"]}) assert "bill_to" not in payload def test_conflict_rejected(): with pytest.raises(AssertionError): P(bill_to="t", bill_to_goes_to_body=False).validate()CI 加一条:HuggingFaceEndpointEmbeddings单测必须覆盖bill_to进入 body、additional_headers合并进请求头。
八、排查清单
- 传
bill_to报extra fields not permitted?→ 字段未建模,需添加到 model。 - 自定义 header(traceparent 等)传不进去?→
_embed_texts没透传additional_headers。 - 计费归属不对?→ 确认
bill_to进了请求 body 而非 header。 - pydantic 是否
extra='forbid'?→ 要么放宽,要么显式声明字段。 - 自托管 TEI 是否需要
bill_to?→ 自建端点可能忽略,但 cloud endpoint 通常需要。 - 是否有其他隐藏参数(如
truncate)也该透传?→ 统一走策略对象扩展。
九、小结
HuggingFaceEndpointEmbeddings缺少bill_to与additional_headers参数,根因是封装层只暴露了最小可用参数,既未在 model 中声明字段,也没在请求组装处透传,导致企业场景下的计费归属与自定义 header 透传无法使用。第一层补上两个字段并注入请求;第二层用LangChainHfEndpointEmbeddingsPolicy把"body 参数 / header 参数"的归属固化成单一事实来源;第三层用 pytest 守护透传语义。LLM/嵌入封装器的通用原则:底层 endpoint 支持的计费与透传能力,都应被显式建模并默认安全透传,而不是留给用户去 monkey-patch。