LLM 上层设施 基于 Langchain 复现 Baseline
Baseline的代码是一个简单的补全解法,没有太多新东西,那来复习下 Langchain 调 API 吧。现在已经是 LCEL(LangChain Expression Language) 的时代了,就只用新写法了。
先从基础的 API 讲起。
OpenAI API 的标准是日前大部分模型服务商都在遵循的,这里我们就以OpenAI Chat API为例来讲解。本次代码中,dashscope 也提供的是OpenAI Chat API风格的接口。
OpenAI Chat API Request Body
OpenAI 官方的接口 Endpoint 是 POST https://api.openai.com/v1/chat/completions
,带有认证头 Authorization
,认证头是 Bearer
格式,token是 API Key。
下文中是所有的字段,但是常用的只有 messages
和 model
,其次是生成参数。回复常用的也只有 choices
。
messages Array<Message>
Required
Messages 字段是一个数组,每个元素是一个 message 对象。
Message 对象有 4 种,分别是 System
, User
, Assistant
, Tool
。Function
类 message 已经被废弃。
System Message: Message
系统消息是由系统生成的消息,不需要用户提供。一般用于基本的 prompt 工程。
role
: 总是system
,Requiredcontent
: 系统消息内容,Requiredname
: 参与者的名字,Optional
这里的参与者名字是指这个消息的来源,用于在多用户的情况下区分不同用户的消息,它不太常用。
例如,一个系统消息可以是:
{
"role": "system",
"content": "Hello, how can I help you?",
"name": "developer"
}
User Message: Message
用户消息是由用户提供的消息,需要用户提供。一般用于用户输入。
role
: 总是user
,Requiredcontent
: 用户消息内容,Requiredname
: 参与者的名字,Optional
与系统消息唯一的区别是 role
字段。
例如,一个用户消息可以是:
{
"role": "user",
"content": "What's the weather like today?",
"name": "Alice"
}
Assistant Message: Message
助手消息是由助手生成的消息,不需要用户提供。一般用于助手回复。
助手是指的语言模型能调用的其它一些工具,例如知识库、搜索引擎等。
role
: 总是assistant
,Requiredcontent
: 助手消息内容,Optional,因为有 tool calls 作为content
的实际内容。如果 tool calls 为空,则 content 必须提供。-
tool_calls
: 函数调用,它也是一个列表,每个对象包含如下的字段,Optional,但如果 content 为空,则 tool calls 必须提供。name
: 函数名,Requiredarguments
: 函数参数,是一个对象,Requiredid
: 函数调用的唯一标识,Required
例如,一个助手消息可以是:
{
"role": "assistant",
"name": "AssistantBot",
"tool_calls": [
{
"id": "123456",
"type": "function",
"function": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}
]
}
Tool Message: Message
工具消息是助手消息的返回结果。
role
: 总是tool
,Requiredcontent
: 工具消息内容,Requiredtool_call_id
: 对应的助手消息的tool_call
的id
,Required
例如,一个工具消息可以是:
{
"role": "tool",
"content": "Sunny",
"tool_call_id": "123456"
}
model String
Required
模型名称,例如 gpt-4o
。
frequency_penalty Float
Optional
默认是 0,取值在 -2 到 2 之间。正值会给重复的词语更高的惩罚。
logit_bias Map<Integer, Float>
Optional
默认是空,是一个整数到浮点数的映射。整数是 token 的索引,浮点数是对应的 logit 偏置。
logprobs Boolean
Optional
默认为 false
。如果为 true
,返回的结果会包含 logprobs 字段,这个字段包含了每个 token 的 logprobs。
max_tokens Integer
Optional
返回的结果最多包含的 token 数。
n Integer
Optional
返回得到的结果的数量,默认为 1。
presence_penalty Float
Optional
默认是 0,取值在 -2 到 2 之间。正值会给出现的词语更高的惩罚。
具体 presence_penalty 和 frequency_penalty 的区别可以参考 OpenAI 文档。
response_format Object
Optional
必须是 { "type": "json" }
或 { "type": "text" }
。默认为后者。使用 json
会强制模型返回一个 json
格式的 message
,使用 text
会返回一个文本格式的结果。
seed Integer
Optional
随机种子,无需解释。
service_tire String
Optional
服务等级可用default
和auto
,默认为auto
。
如果设置了服务等级,返回结果也会包含服务等级的字段。
stop Array<String> | String
Optional
停止词,如果返回结果中包含了停止词,会提前结束。
stream Boolean
Optional
如果为 true
,会使用流模式返回。
stream_options Object
Optional
只有一个字段,include_usage
,如果为 true
,返回时会包含一个 usage
字段。
对于其它的所有对象,usage
都是空。在 [DONE]
信息被返回前,会多发一个对象。这个对象的返回信息是空,但是 usage
字段会包含一些信息。
temperature Float
Optional
默认是 1,取在 0 到 2 之间。
top_p Float
Optional
默认是 1,取在 0 到 1 之间。
tools Array<Tool>
Optional
支持的工具列表,每个工具是一个对象。
工具对象包含如下字段:
type
: 工具类型,目前只支持function
,Required。-
function
: 函数对象,Required。description
: 函数描述,Optional。name
: 函数名,Required。-
parameters
: 函数参数,是一个对象,Optional。如果函数没有参数,可以为空对象。name
: 参数名,Required。type
: 参数类型,Required。description
: 参数描述,Optional。
tool_choice String | Object
Optional
控制模型可用的工具。如果是字符串,可以为 none
,禁止所有工具,auto
,自动选择工具,required
,必须使用工具。
使用对象时,每个对象都模型必须使用的工具。
type
: 工具类型,目前只支持function
,Required。-
function
: 函数对象,Required。name
: 函数名,Required。
parallel_tool_calls Boolean
Optional
使用并行工具调用。
user String
Optional
用户 ID。
OpenAI Chat API Response Body
返回对象有两种,chat completion object
和chat completion chunk object
。
Chat Completion Object
id
: 请求的唯一标识。created
: 请求的时间,Unix 秒时间戳。model
: 模型名称。service_tier
: 服务等级。system_fingerprint
: 系统指纹,代表了调用时的系统环境。object
: 返回对象的名称,总是chat.chat.completion
。-
usage
: 包含一些计费信息。total_tokens
: 总 token 数。prompt_tokens
: prompt 的 token 数。completion_tokens
: 返回的 token 数。
-
choices
: 返回的结果,是一个数组。finish_reason
: 结束原因,有stop
,碰到停止词,length
,达到最大 token 数,content_filter
,被 censor 了,tool_calls
,模型请求了工具。index
: 结果的索引。logprobs
: logprobs 字段。-
message
: 消息对象。content
: 消息内容。role
: 消息角色。-
tool_calls
: 工具调用。id
: 工具调用的唯一标识。type
: 工具类型。-
function
: 函数对象。name
: 函数名。arguments
: 函数参数。
Chat Completion Chunk Object
如果是流模式,会返回多个 chat completion chunk object
,最后会返回一个 [DONE]
作为结束标志。
id
: 请求的唯一标识。created
: 请求的时间,Unix 秒时间戳。model
: 模型名称。service_tier
: 服务等级。system_fingerprint
: 系统指纹,代表了调用时的系统环境。object
: 返回对象的名称,总是chat.chat.completion.chunk
。usage
: 同上。choices
: 同上。
Langchain 基础使用
Langchain 如今的基础是 LCEL,基本思想是管线化,即把每个组件变成一个管线,然后把管线串联起来。因为 python 没有自定义符号,所以使用|
来表示管线,这也是 unix 管道的符号。
管线连接的方法是invoke
,即上一个管线的invoke
输出是下一个管线invoke
的输入。
本文只需要一个简单的模型补全,因此我们的管线是 prompt_template | model | parser
。
Prompt Template
对话补全使用from langchain_core.prompts.ChatPromptTemplate
。
创建对话模版使用from_messages
构造器,它接受一个元组列表,元组的第一个元素是消息的角色,第二个元素是消息的内容。
Langchain 使用简单的 f-string 模版,因此变量用{}
包裹。
例如:
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "Hello, how can I help you?"),
("user", "{problem}")
])
input_str = prompt.invoke({"problem": "What's the weather like today?"})
这里我们使用这样一个模版:
prompt = ChatPromptTemplate.from_messages([
("system", '你是一个逻辑推理专家,擅长解决逻辑推理问题。以下是一个逻辑推理的题目,形式为单项选择题。所有的问题都是(close-world assumption)闭世界假设,即未观测事实都为假。请逐步分析问题并在最后一行输出答案,最后一行的格式为"答案是:A"。'),
("user", """### 题目:
{problem}
### 问题:
{question}
{options}""")
])
Model
Langchain 直接支持 Dashscope,因此我们可以直接使用 Dashscope 的模型。
from langchain_community.llms import Tongyi
model = Tongyi()
需要在环境变量中设置DASHSCOPE_API_KEY
。
Parser
直接使用一个StrOutputParser
。
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
管线串联
chain = prompt | model | parser
然后这样调用即可:
def format_options(options):
return '\n'.join(
[
f'{chr(ord("A") + i)}: {option}'
for i, option in enumerate(options)
]
)
res = chain.invoke({
"problem": "有一个列表,找出该列表的最后一个元素。\n\n下列选项中哪个是列表 `[a, b, c, d]` 的最后一个元素?",
"question": "选择题 1:",
"options": format_options(["a", "b", "c", "d"])
})
这样就完成了一个简单的补全。
处理下数据即可复现baseline。