使用url接入langchain的OpenAI或者ChatOpenAI
在上一篇文章(http://hexo.sakebow.cn/2024/12/10/LLM/langchain-custom-llm/)中,我们探讨了如何自定义LLM类。但是看到最新的LangGraph版本中,主要利用的是OpenAI或者ChatOpenAI,并使用了ChatOpenAI独有的bind_tool方法,使得图结构有了更为丰富的动作与功能,这让我非常眼红。于是本文就探讨了OpenAI或C
前言
在上一篇文章中,我们探讨了如何自定义LLM
类。但是看到最新的LangGraph
版本中,主要利用的是OpenAI
或者ChatOpenAI
,并使用了ChatOpenAI
独有的bind_tool
方法,使得图结构有了更为丰富的动作与功能,这让我非常眼红。于是本文就探讨了OpenAI
或者ChatOpenAI
包装自定义LLM
的方法。
OpenAI为什么可以?
有些人已经比较熟悉了,为什么偏偏包括OpenAI
在内的大模型厂商都可以使用url
+api-key
的模式访问信息并获取返回结果。这里还是再度解析一下。
我们找到Python
文件中的from openai import OpenAI
,并选择进入OpenAI
看一看原理,我们可以发现,OpenAI
类继承自SyncAPIClient
,而SyncAPIClient
继承自BaseClient[httpx.Client, Stream[Any]]
,类内有一个成员变量:_client: httpx.Client
。
核心的部分在:
def request(
self,
cast_to: Type[ResponseT],
options: FinalRequestOptions,
remaining_retries: Optional[int] = None,
*,
stream: bool = False,
stream_cls: type[_StreamT] | None = None,
) -> ResponseT | _StreamT:
if remaining_retries is not None:
retries_taken = options.get_max_retries(
self.max_retries
) - remaining_retries
else:
retries_taken = 0
return self._request(
cast_to=cast_to,
options=options,
stream=stream,
stream_cls=stream_cls,
retries_taken=retries_taken,
)
而_request
中,有一段是这样的
response = self._client.send(
request,
(
stream=stream or
self._should_stream_response_body(
request=request
)
),
**kwargs,
)
其中,send
方法都并不需要太关注源码,直接看注释都明白是在干啥了:
def send(
self,
request: Request,
*,
stream: bool = False,
auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
) -> Response:
"""
Send a request.
The request is sent as-is, unmodified.
Typically you'll want to build one with `Client.build_request()`
so that any client-level configuration is merged into the request,
but passing an explicit `httpx.Request()` is supported as well.
See also: [Request instances][0]
[0]: /advanced/clients/#request-instances
"""
也就是说,OpenAI
的本质就是一个巨大的网络请求客户端,而url
与api-key
就是访问客户端的两个参数。
OpenAI怎么传参?
那么知道了这些之后,我们又应该怎么找规律,从而把自己的url
和api-key
塞进去呢?
首先我们注意到其中有一个方法:
@property
@override
def auth_headers(self) -> dict[str, str]:
api_key = self.api_key
return {"Authorization": f"Bearer {api_key}"}
这个和MindIE
的使用方法相当一致。我们接着找,然后在这里找到了一点蛛丝马迹:
@property
def default_headers(self) -> dict[str, str | Omit]:
return {
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": self.user_agent,
**self.platform_headers(),
**self.auth_headers,
**self._custom_headers,
}
在这里,self.auth_headers
中构建的字典在这里被展开了,成为了新字典中的若干个字段。如果你对HTTP
请求略有基础,你会发现,原来内置的部分属性Accept
、Content-Type
、User-Agent
都是仅出现在请求头中的内容。也就是说,这个api-key
实际上是应当放在请求头里面一起携带过去。
而url
最终也将成为BaseClient
类中的一个参数,传入httpxRequest._models.Request
类:
return Request(
method,
url,
content=content,
data=data,
files=files,
json=json,
params=params,
headers=headers,
cookies=cookies,
extensions=extensions,
)
OpenAI怎么兼容?
既然大概知道了OpenAI
的原理,那么关键就是兼容自己的LLM
了。
首先,我们需要一个api
的url
,也就是请求返回结果是按照OpenAI
接口格式返回的一个数据接口,一般情况下是以v1/chat/completions
为结尾的。
然后,我们需要一个api
的key
,这个key
必须得是Bearer
开头的,否则就没办法兼容OpenAI
了,除非你愿意花这个时间把整个OpenAI
的接口实现一遍,然后在自定义api_key
的时候做出不一样的行为。
然后,拿到你的api-key
:Bearer <your-api-key>
我们就可以开开心心地访问了:
llm = OpenAI(
base_url="<your-api-url>",
api_key="<your-api-key>"
)
P.S. 一般情况下,厂商会帮你隐去这个
Bearer
,你只需要关注<your-api-key>
就好了。而如果厂商没有帮你隐去,你也需要知道,在当前开发场景下,到底要不要加上Bearer
。
如果你比较细心的话,你会发现通义千问的方法也是这个:
llm = OpenAI(
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="<your-dashscope-key>"
)
这也反向证明了我们的正确性。
ChatOpenAI如何兼容?
我们同样从源码中找到答案。
首先,找到from langchain_openai import ChatOpenAI
,其实这也是同等于from langchain_openai.chat_models.base import ChatOpenAI
。
在这里,我们找到有一个client
属性与root_client
属性。其中:
self.root_client = openai.OpenAI(
**client_params, **sync_specific
)
self.client = self.root_client.chat.completions
看出来了吗?ChatOpenAI
本质上就是openai.OpenAI
的封装,root_client
属性就是openai.OpenAI
的实例,client
属性是v1/chat/completions
接口的实例。
这就没什么悬念了。所以ChatOpenAI
实际上与OpenAI
一样,只需要这样:
llm = ChatOpenAI(
base_url="<your-api-url>",
api_key="Bearer <your-api-key>",
model="<your choice>"
)
更多推荐
所有评论(0)