logo
12

文心一言插件保姆级教程

AI通用大模型是信息化时代的又一次大革命,我们如果不跟上这次革命就会被无情的淘汰,文心一言最近发布了插件版本,使得传统互联网业务接入大模型成为可能,虽然现在还存在各种问题,但是只要大家一起努力终将实现真正意义的AI时代,下面针对文心一言开发者文档中缺失的细节进一步说明,以加快插件开发和调试的效率。

开发环境准备

首先你得熟悉python开发环境,如果不了解可以寻求相关的帮助。我们使用现在流行的conda来创建一个虚拟的python环境。
因为插件是以http方式接入的,所以你的项目必须实现web restFul接口,建议采用python中的Flask框架,所以先安装Flask包。
  
  
  
  
  
  
pip install flask[async]
pip install flask_cors

下载插件项目demo

这个操作就不说了,从官方开发文档中就可以找到。

demo文件结构如下

  
  
  
  
  
  
yiyan_plugin_demo/ # 插件demo注册的根目录
|—.well-known
|— ai-plugin.json #插件主描述文件
|— openapi.yaml #插件API服务的标准描述文件
|— example.yaml #插件示例描述文件,因为这个文件大小限制在300个字符,所以可以编写多个,然后在ai-plugin.json中examples下添加访问的地址即可
|— logo.png #插件的图标文件
|— demo_server.py #插件注册服务,可以启动到本地
|— requirements.txt #启动插件注册服务所依赖的库,要求python >= 3.7
|— readme.md # 说明文件

修改ai-plugin.json文件

这个部分官方文档已经说明的很详细了,不再重复
  
  
  
  
  
  
{
"schema_version": "v1",
"name_for_human": "单词本", # 主要是最终呈现在前端的名字标识
"name_for_model": "wordbook_123", # 英文字符,全局唯一,主要是输入给模型的名字标识
"description_for_human": "个性化的英文单词本,可以增加、删除和浏览单词本中的单词,并可以按要求从单词本中随机抽取单词生成句子或段落",
"description_for_model": "帮助用户管理单词本,可以增加、删除、浏览单词本,背单词时可以指定随机抽取单词本中若干个单词,生成句子会段落",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "PLUGIN_HOST/openapi.yaml"
},
"logo_url": "PLUGIN_HOST/logo.png",
"contact_email": "support@example.com",
"legal_info_url": "http://www.example.com/legal"
}
里边的PLUGIN_HOST开发期间换成本地地址http://localhost:8081,上线后换成你插件服务地址即可

修改openapi.yaml文件

这个文件是向文心一言提供识别你插件能够提供那些服务的关键文件,只有这个文件里边每个服务的描述越准确,大语音模型识别的准确性才会高
  
  
  
  
  
  
openapi: 3.0.1
info:
title: 单词本
description: 个性化的英文单词本,可以增加、删除和浏览单词本中的单词,背单词时从已有单词本中随机抽取单词生成句子或者段落。
version: "v1"
servers:
- url: PLUGIN_HOST # 替换成你实际服务启动的地址和端口,调试环境可以是localhost,正式上线需要换成公网IP
paths:
/get_wordbook:
get:
operationId: getWordbook
summary: 展示单词列表
responses:
"200":
description: 列表展示完成
content:
application/json:
schema:
$ref: "#/components/schemas/getWordbook"
/generate_sentences:
post:
operationId: generateSentences
summary: 背单词,生成句子
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/generateSentences"
responses:
"200":
description: 句子生成成功
content:
application/json:
schema:
$ref: "#/components/schemas/responseSentences"
/add_word:
post:
operationId: addWord
summary: 在单词本中添加一个单词
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/addWord"
responses:
"200":
description: 单词添加成功
content:
application/json:
schema:
$ref: "#/components/schemas/messageResponse"
/delete_word:
delete:
operationId: deleteWord
summary: 从单词本中删除一个单词
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/deleteWord"
responses:
"200":
description: 单词删除成功
content:
application/json:
schema:
$ref: "#/components/schemas/messageResponse"
components:
schemas:
getWordbook:
type: object
required: [wordbook]
properties:
wordbook:
type: array
items:
type: string
description: 单词本单词列表
addWord:
type: object
required: [word]
properties:
word:
type: string
description: 需要添加到单词本中的一个单词
deleteWord:
type: object
required: [word]
properties:
word:
type: string
description: 需要删除的单词
generateSentences:
type: object
required: [word_number]
properties:
word_number:
type: integer
description: 几个单词
responseSentences:
type: object
required: [words]
properties:
words:
type: array
items:
type: string
description: 随机抽取的英文单词
messageResponse:
type: object
required: [message]
properties:
message:
type: string
description: 回复信息
这个文件包含2个部分,第一个是paths,这个部分是对插件提供哪些服务,使用get、post等协议,以及用自然语言描述该服务的功能,入参格式,出参格式等的综合配置,第2部分是对具体入参、出参字段名称,数据类型等的配置

修改example.yaml文件

  
  
  
  
  
  
version: 0.0.1
examples:
- context: # 对话历史
- role: user
content: 请帮我添加一个单词Hello
- role: bot
# 触发插件
plugin:
# 应当触发的接口operationId
operationId: addWord
# 思考过程,对触发有帮助
thoughts: 这是一个插入单词到单词本的需求
requestArguments:
word: "Hello"
query: 请帮我添加一个单词Hello
- role: user
content: 展示单词列表
- role: bot
# 触发插件
plugin:
# 应当触发的接口operationId
operationId: getWordbook
# 思考过程,对触发有帮助
thoughts: 这是一个展示单词本的需求
requestArguments:
query: 展示单词列表
- role: user
content: 请帮我添加一个单词World
- role: bot
# 触发插件
plugin:
# 应当触发的接口operationId
operationId: addWord
# 思考过程,对触发有帮助
thoughts: 这是一个插入单词到单词本的需求
requestArguments:
word: "World"
query: 请帮我添加一个单词World
- role: user
content: 我要背两个单词
- role: bot
# 触发插件
plugin:
# 应当触发的接口operationId
operationId: generateSentences
# 思考过程,对触发有帮助
thoughts: 这是一个从单词本中抽取单词生成句子的需求
requestArguments:
word_number: 2
query: 我要背两个单词
- role: user
content: 删除单词Hello
- role: bot
# 触发插件
plugin:
# 应当触发的接口operationId
operationId: deleteWord
# 思考过程,对触发有帮助
thoughts: 这是一个从单词本中删除单词的需求
requestArguments:
word: "Hello"
query: 删除单词Hello
# 反例,无需触发此插件的任一某个接口
- context:
- role: user
content: 怎么养成记单词的习惯?
- role: bot
# 无需触发
plugin:
thoughts: 我不需要使用以上工具
这个文件是给大语音模型交互的参考模式,可以进一步提高人机交互的准确性,能够精准的告诉大模型调用插件的那些功能API

编写自己插件的API

插件的所有API都是以http方式提供的,包括提供给文心一言读取插件的logo、描述、API以及学习样例,这里demo程序里边有的不再重复,重点补充一下内容

API如何准确获取大模型的入参

在和文心一言对话的时候,把需要传递给插件API的参数内容最好用括号包起来,例如:调用“背单词”插件添加单词的接口,那么在跟文心一言的对话框中输入:添加单词(hello),这个效果会比:添加单词hello好一些,不容易引起歧义,特别是全中文描述一个问题的时候,大模型很难区分出那个部分是希望传递给API的参数。

API返回的参数如果要被大语言模型接受就要配置到prompt参数中

文心一言只能接受API以json格式的输出,只能识别prompt的内容是需要大模型进一步处理的,所以如果返回的结果还需要交给大模型进一步处理就要组合返回的数据和提示词一起形成prompt,例如:
  
  
  
  
  
  
# 返回结果
prompt=f"解析JSON格式的数据({result}),用表格的方式显示出来"
return make_json_response({"message":"订餐信息获取完成" , "prompt": prompt})

如何使用SSE和文心一言交互

文心一言不仅仅提供一问一答的模式和插件API交互,还提供了流式交互模式,这个需要插件API使用Flask的sse来实现该API。
  
  
  
  
  
  
def generate_sse():
count = 0
while True:
time.sleep(1)
data={"count":count,"actionName":"连接网页","actionContent":"开始连接网页URL"}
# json转成字符串
json_str = json.dumps(data)
count+=1
if count % 10 == 0:
break
yield f"data:{json_str}\n\n"
# SSE流方式输出
@app.route('/events_demo', methods=['POST'])
def events_demo():
return Response(stream_with_context(generate_sse()))
同时api描述文件openapi.yaml里边的responses里边的content格式要改成text/event-stream
  
  
  
  
  
  
def generate_sse():
count = 0
while True:
time.sleep(1)
data={"count":count,"actionName":"连接网页","actionContent":"开始连接网页URL"}
# json转成字符串
json_str = json.dumps(data)
count+=1
if count % 10 == 0:
break
yield f"data:{json_str}\n\n"
# SSE流方式输出
@app.route('/events_demo', methods=['POST'])
def events_demo():
return Response(stream_with_context(generate_sse()))
这样插件API的处理过程就可以逐步显示到文心一言的前端。

各种配置文件在插件注册的时候被读取到

插件的配置文件也是以http方式给个给文心一言平台的,所以这些配置文件也要一API的方式提供,demo中已经提供了logo、ai-plugin.json、openapi.yaml的,这里补充example.yaml的
  
  
  
  
  
  
@app.route("/.well-known/example.yaml")
async def example_spec():
with open(".well-known/example.yaml", encoding="utf-8") as f:
text = f.read()
return text, 200, {"Content-Type": "text/yaml"}
如果有多个example文件,就写多个api,再把对应的api url配置到ai-plugin.json的examples项下。

鉴权的实现

使用Flask框架实现异步路由的鉴权时有几个坑需要注意
  1. 编写鉴权装饰器的时候需要明确装饰器返回的是异步过程,而且return的时候要await前缀说明
  
  
  
  
  
  
# 定义一个用户级别鉴权装饰器,对用户的token进行验证
def service_level_auth(f):
@wraps(f)
async def wrapper(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if not auth_header:
return make_json_response({"message": "缺少授权头","errCode":-100})
print(f"用户的token:{auth_header}")
# 添加相关的鉴权逻辑
return await f(*args, **kwargs)
return wrapper
  1. 在需要权限保护的路由上使用该装饰器
  
  
  
  
  
  
# 使用装饰器保护路由
# 本插件的功能清单
@app.route("/todo_list", methods=['POST'])
@service_level_auth

目前文心一言还存在的问题

  1. 插件注册过程中的一些错误提示toast显示时间太短,还没有看清就消失了
  2. 插件有效时间太短,经常就不起作用了(触发不了插件api),需要重新打开文心一言,重新选择插件
  3. 插件返回的prompt长度有限,而且文档中没有说明最大长度多少
  4. 不支持一个输入触发多个API的功能,最好实现类似LangChain的功能
  5. 对返回的json解析不是很准确,按照文档中描述的errCode、actionName、actionContent等保留字好像没有识别并按照既定的逻辑处理
评论
用户头像