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。