大模型应用开发:为产品创建一个AI客服/智能助手

欢迎阅读本系列文章!我将带你一起探索如何使用OpenAI API来开发GPT应用。无论你是编程新手还是资深开发者,都能在这里获得灵感和收获。

本文将继续展示AI助手的开发方式,在OpenAPI中它的名字是Assistants。

什么是Assistants?

在之前的文章中我演示了插件的使用方法,比如查询实时天气、进行数学运算等,这些都是大模型自身做不到的事情,因此可以说插件的主要作用是扩展了大模型的处理能力。那么Assistants能干什么呢?

Assistants的主要作用是强化大模型在某方面的应用能力,比如目前已经大范围使用的AI客服和知识库助手,它们可以准确的理解用户的问题,并在限定的知识范围内进行精准回答。另外借助Assistants的能力,我们还可以做更多有趣的事情,比如让它按照指定的规范对代码进行Review,按照某种指定的风格或者模式来进行文学创作,等等。

本文我们将通过一个AI客服来演示Assistants的使用方法。先看效果:

这里我开发了一个空气净化器的AI客服,然后用户向AI客服提了四个问题,前三个问题AI都理解准确并回答正确,回答内容全部来源于产品手册,最后一个问题脱离了产品手册的内容范围,AI只能拒绝回答。

Assistants的运行原理

工欲善其事,必先知其理。在编写Assistants的代码之前,我们先要搞清楚它是怎么运行的,然后写代码的时候才能有的放矢、逻辑清晰。

请看下边这张图:

1、创建智能助手(Assistant):这一步我们要给智能助手下个定义,包括起个名字、声明它的能力、使用的大模型版本、增强能力的方式(执行代码、从知识库检索、调用外部函数)等。

2、创建用户会话(Thread):会话就是用户和智能助手之间的一次聊天,GPT可以通过会话方便的管理聊天上下文。

3、添加用户消息到会话(Message):就是用户向智能助手说的话,必须添加到会话中。

4、在会话中运行智能助手(Run):将会话和智能助手进行绑定,运行智能助手来处理用户的消息。这一步实际上会创建一个智能助手的执行对象,然后把这个执行对象添加到一个处理队列中,最终处理状态会更新到运行对象中。

5、获取GPT响应的消息(Response):通过不断检查运行对象的状态,获取智能助手的响应结果。

实现AI客服

我们这里就按照Assistant的运行原理来实现一个AI客服。

产品手册

首先我们要准备一个产品手册,随便写点什么都行,为了方便大家,可以直接下载我这个:

https://github.com/bosima/openai-api-demo/blob/main/niubiclean-book.txt

然后我们需要将这个文件上传到OpenAI,注意把文件放到程序能够访问到的地方。

niubiclean_book = client.files.create(
    file=open("niubiclean-book.txt", "rb"),
    purpose="assistants"
)

purpose 可选的值有两个:fine-tune 和 assistants。

创建助手

这里使用的是 client.beta.assistants.create 来创建客服,因为assistants还没有正式发布,所以这里的包空间名称中包含了一个beta,正式发布时会去掉。具体代码如下:

waiter = client.beta.assistants.create(
    name="牛逼净化器智能客服",
    description="24小时为您服务",
    instructions="你是牛逼净化器公司的智能客服,请引用文件中的内容回答问题,表达要通俗易懂、尽量简短;若问题超出文件内容,请委婉拒绝。",
    model="gpt-3.5-turbo-1106",
    tools=[
        {
            "type": "retrieval",
        }
    ],
    # 知识文件,通过File接口上传的
    file_ids=[niubiclean_book.id]
)

简单说下这几个参数:

name:智能助手的名字,随便起。

description:智能助手的简介描述,最长 512 字符。

instructions:给智能助手的指令,也就是提示词,让智能助手按照这里的提示词提供服务。这里我用了一个常见的提示词套路,让它扮演一个角色,有什么样的能力,如何回答用户的问题等。最长 32768 字符。

model:使用的GPT大模型,这里用便宜的3.5,你也可以换成GPT-4。

tools:assistants开启的工具,共有三种类型:code_interpreter、retrieval、function。

  • code_interpreter:是代码解释器,能让GPT在一个沙盒环境中执行python代码,能从文件读取数据,也能生成文件,需要通过instructions提示assistant可以执行代码。
  • retrieval:从文件检索内容,这里我们的AI客服只能根据产品手册回答问题,所以这里只开启了retrieval的能力。
  • function:和聊天插件的使用方法一样,调用执行函数,根据执行结果向用户返回内容。

file_ids:指定GPT要检索的文件Id,可以设置多个。这里设置为我们上一步上传的手册。

创建用户会话

使用 client.beta.threads.create 创建用户会话,具体代码如下。

thread_userjia = client.beta.threads.create(
    metadata={
        "姓名": "用户甲",
        "年龄": 36,
        "性别": "男"
    }
)

metadata是可选的,可以设置一些附加信息,无固定属性,key-value格式即可。

添加用户消息到会话

我们其实可以在创建 thread 时初始化一些消息,不过既然要对话,演示下如何添加消息更有意义。

使用 client.beta.threads.messages.create 来创建一条用户消息,并绑定到某个会话,代码如下:

message = client.beta.threads.messages.create(
        thread_id=thread_userjia.id,  
        role="user",          
        content="净化器有什么功能?",
    )

这里有三个参数:

  • thread_id:消息绑定到的会话Id。
  • role:消息的角色,目前只支持 user,只能向其中添加用户消息。至于完整的聊天上下文,GPT内部会自动维护。
  • content:消息内容,这个很好理解。

在会话中运行智能助手

使用 client.beta.threads.runs.create 来运行智能助手,代码如下:

run = client.beta.threads.runs.create(
        thread_id=thread_userjia.id,
        assistant_id=waiter.id,
    )

这里有两个关键的参数:

  • thread_id:要在哪个会话中运行智能助手。
  • assistant_id:要运行哪个智能助手。

这里还有一些其它的参数,比如model、instructions、tools等,使用它们会覆盖我们在创建 assistant 设置的参数。

获取智能助手的回应

运行智能助手后得到的返回值 run 是一个对象,代表运行在会话中的一个执行,这个执行是通过队列异步处理的,我们不能立即得到执行结果,需要定期检查 run 的状态,处理完毕了才能获取到GPT的回应消息。

先看检查状态的处理:

while run.status == "queued" or run.status == "in_progress":
    time.sleep(1)
    run = client.beta.threads.runs.retrieve(
        thread_id=thread_userjia.id,
        run_id=run.id,
    )

run 有多个状态: queued, in_progress, requires_action, cancelling, cancelled, failed, completed, expired,这个例子中如果不是 queued 或者 in_progress 状态就代表已经有结果了。requires_action 是智能助手使用 function 工具时才会存在的状态,这个例子不涉及。

|-----------------|-----------------------------------------------------------------------------------|
| 状态 | 含义 |
| queued | 创建run之后 或者 使用function时确定了要调用的function及其参数 之后,就会进入这个状态,这个状态很短,马上会进入 in_progress状态。 |
| in_progress | 使用模型或者tools处理消息。 |
| completed | 本次运行成功完成,可以读取GPT响应的消息了。 |
| requires_action | 使用function时,一旦模型确定要调用的function及其参数,run将进入这个状态。 |
| expired | function执行的时间太长或者整个run运行的时间太长,达到了过期阈值(大约10分钟)。 |
| cancelling | 可以在queued和in_progress状态时发起取消,将进入这个状态。 |
| cancelled | 已成功取消。 |
| failed | 您运行失败了,可以在 run.last_error 中获得失败原因。 |

使用 client.beta.threads.messages.list 获取GPT响应消息,代码如下:

 if run.status=="failed":
    print(run.last_error.message)
else:
    messages = client.beta.threads.messages.list(
        thread_id=thread_userjia.id, order="asc", after=message.id
    )
    print("牛逼智能客服:",extract_message_content(messages.data[0]),'\n')

获取响应消息时用到了3个参数:

  • thread_id:会话Id。
  • order:消息排序,asc代表正序,也就是先产生的消息在前边。
  • after:指定消息的起始位置,因为我们要获取GPT针对某条用户消息的响应,所以这里通过after指定获取某条用户消息之后的消息,也就是GPT的响应消息。

最后我们还使用了一个函数来提取消息内容:extract_message_content,代码如下:

def extract_message_content(message):
    # Extract the message content
    message_content = message.content[0].text
    annotations = message_content.annotations

    # Iterate over the annotations and add footnotes
    for index, annotation in enumerate(annotations):
        # Replace the text with a footnote
        # print(annotation.text)
        message_content.value = message_content.value.replace(annotation.text, ' ')
    
    return  message_content.value

注意这里有一个annotation的概念,中文就是注解的意思。因为AI客服生成的内容可能来自多个产品文档,有了注解,用户就可以通过它跳转到相关的文档进行详细阅读。这个和论文中的引用注解是同一种方式。

不过我们这里的产品手册比较简单,所以就把注解都替换成空字符串了。完整的处理方法可以参考下边这个:

# Extract the message content
message_content = message.content[0].text
annotations = message_content.annotations
citations = []

# Iterate over the annotations and add footnotes
for index, annotation in enumerate(annotations):
    # Replace the text with a footnote
    message_content.value = message_content.value.replace(annotation.text, f' [{index}]')

    # Gather citations based on annotation attributes
    if (file_citation := getattr(annotation, 'file_citation', None)):
        cited_file = client.files.retrieve(file_citation.file_id)
        citations.append(f'[{index}] {file_citation.quote} from {cited_file.filename}')
    elif (file_path := getattr(annotation, 'file_path', None)):
        cited_file = client.files.retrieve(file_path.file_id)
        citations.append(f'[{index}] Click <here> to download {cited_file.filename}')
        # Note: File download functionality not implemented above for brevity

# Add footnotes to the end of the message before displaying to user
message_content.value += '\n' + '\n'.join(citations)

完整示例

我在完整的示例程序中向智能助手循环提出了四个问题,每个问题都需要重新创建一个run,然后再检查状态,获取响应结果。

需要完整代码的同学请访问Github:

https://github.com/bosima/openai-api-demo/blob/main/assistants_demo.ipynb


以上就是本文的主要内容,有兴趣的同学快去试试吧,效果绝对震惊你的小伙伴!

如需GPT账号、学习陪伴群、AI编程训练营,推荐关注小册:大模型应用开发 | API 实操

关注萤火架构,加速技术提升!

相关推荐
营赢盈英1 个月前
OpenAI GPT-3 API: What is the difference between davinci and text-davinci-003?
ai·gpt-3·openai·openai api
营赢盈英1 个月前
OpenAI converting API code from GPT-3 to chatGPT-3.5
人工智能·chatgpt·gpt-3·php·openai api
营赢盈英2 个月前
how can I train a OpenAI fine tuned model with more prompts
人工智能·深度学习·ai·openai api
营赢盈英2 个月前
Why is OpenAI image generation Api returning 400 bad request in Unity?
ai·json·openai api·post·unitywebrequest·unitygameengine
营赢盈英2 个月前
Azure OpenAI and token limit
ai·chatgpt·asp.net·azure·openai api
营赢盈英2 个月前
TypeError: expected string or buffer - Langchain, OpenAI Embeddings
langchain·azure·embeddings·openai api·rag
营赢盈英2 个月前
Trying to install openai in chaquopy in android studio but getting build failed
android·ide·ai·android studio·openai api·chaquopy
营赢盈英2 个月前
Azure web app has no access to openai private endpoint in virtual network
azure·openai api·web app·webapp·webapps·virtual network
营赢盈英2 个月前
How to handle the response OpenAI Text-To-Speech API in Node.js?
ai·node.js·openai·openai api·response
营赢盈英2 个月前
Why I‘m getting 404 Resource Not Found to my newly Azure OpenAI deployment?
ai·azure·service·openai api·cognitive