构建面向业务的生成式人工智能系统——构建 AI 控制器编排界面

94 阅读42分钟

现代企业需要以前所未有的速度设计、生产和交付商品与服务。无论是在线云服务,还是餐饮配送、药品供应、服装销售等领域,响应速度已成为关键。这样一个以事件驱动的经济产生了源源不断的任务,只有同样基于事件驱动、以人为核心的生成式AI系统(GenAISys)才能跟上节奏。

即便是最自动化的工作流程,人类判断依然是根基:当火灾爆发、风暴摧毁基础设施或供应链受阻时,必须由团队而非单纯算法来应对。因此,一个排除人为参与的高级GenAISys是虚幻的。本章从描绘打破用户与AI壁垒,打造协作型多用户聊天机器人的架构开始。

我们首先从宏观层面勾勒出事件驱动的GenAISys界面,展示前几章的核心组件——短期、情景性和长期记忆,多轮对话代理,以及指令场景和数据的双RAG流水线——如何协同工作。随后,为实现响应式系统,我们将编写GenAISys的流程代码,以及管理生成式AI代理的会话代理代码。界面搭建完成后,我们将演示三个用户在一家在线旅行社中的多用户多轮对话,他们的在线会议中将包含一个会话AI代理作为参与者。

这些用户可以选择是否有AI代理参与在线会议。他们可以利用RAG检索指令场景,或直接让生成式AI代理回答问题。本章结束时,我们将拥有一个完整可用的GenAISys界面,为第五章的多模态链式思维扩展做好准备。

简而言之,本章涵盖以下内容:

  • 事件驱动的GenAISys界面架构的高层视图
  • GenAISys界面的低层实操流程图
  • 输入、AI代理和活跃用户的响应控件实现
  • 多轮对话中聊天机器人的事件驱动流程
  • 以AI代理作为参与者的多用户GenAISys对话
  • 会话代理的响应式RAG功能
  • GenAISys界面与AI代理的编排能力

我们的首要任务是定义一个事件驱动的GenAISys界面。

事件驱动型 GenAISys 界面架构

我们的事件驱动型 GenAISys 界面集成了前几章构建的各项功能。该界面将利用 IPython 控件的灵活性,创建一个响应式的事件驱动环境,具体特点包括:

  • 高层任务由事件驱动,触发来源为用户输入
  • 生成式AI任务将激活生成式AI代理的功能

我们首先从宏观层面审视正在构建的程序,如图4.1所示:

image.png

让我们回顾一下前几章已经构建的功能,同时列出本章新增的关键组件:

  • I1 – AI控制器:本章新增的主要组件,是带有响应式控件的生成式AI Python接口,作为AI控制器和编排器运行。
  • I2 – 多用户聊天机器人:支持多位用户同时交互的聊天界面。
  • F1 – 生成式AI模型:继承自之前所有章节,尤其是第三章中基于GPT-4o的生成式AI调用。
  • F2 – 记忆保持:继承自第一章,介绍了不同类型的记忆机制。
  • F3 – 模块化RAG:继承自第三章的指令与数据流水线。
  • F4 – 多功能能力:涵盖第二章和第三章的语义与情感分析,后续第五章将扩展至图像、音频、网页搜索和机器学习等功能。

为了构建此架构,我们将:

  • 构建事件驱动的 GenAISys 界面流程
  • 实现基于 GPT-4o 和 OpenAI 嵌入模型的会话代理
  • 运行一个多用户多轮会话,探索 GenAISys AI控制器和编排器的主要功能

本章及下一章展示 GenAISys 架构主要组件时,未使用箭头连接,这是一种刻意设计,旨在传达核心理念:模块化和架构灵活性。图示不是一成不变的蓝图,而是一个概念工具包,展示了强大的可用组件——I1(AI控制器)、I2(多用户聊天机器人)、F1(生成式AI模型)、F2(记忆保持)、F3(模块化RAG)、F4(多功能能力)——它们是独立且可互操作的模块。这赋予你自由设计系统架构的能力。例如,用户可以将某些功能组件(如F4)作为独立的分布式代理,由控制器按需调用;或者实现完全不同的界面,甚至无界面地运行系统。

然而,本架构重点展示以人为核心的 GenAISys。此配置中,I1(生成式AI的 IPython 接口)作为核心枢纽和编排者。以人为中心的架构保证了完全的控制与透明度,这对于风险规避的企业环境尤为重要。虽然控制流未用箭头标示,但其逻辑是明确的:来自 I2(多用户聊天机器人)的用户交互由 AI控制器管理,控制器再有策略地将任务分配给各功能组件(F1至F4),以生成响应、访问记忆、执行RAG或完成特定功能。这种方法提供了清晰、稳定且可解释的路径,构建面向业务的生成式AI系统。

接下来,我们先探讨场景驱动的任务执行。

构建事件驱动型 GenAISys 界面流程

让我们从构建图4.2所示的 GenAISys 界面开始,利用 IPython 控件创建一个响应式、事件驱动的环境。最终效果是一个动态的多用户聊天界面,配备下拉菜单、文本输入框和复选框——满足人与用户以及生成式AI代理之间实时协作的所有需求。

请打开 GitHub 上 Chapter04 目录中的 Event-driven_GenAISys_framework.ipynb 笔记本(地址:github.com/Denis2054/B…)。环境搭建与前几章一致:

  • OpenAI 环境搭建请参见第一章,包括本章所用的自定义 OpenAI API 调用:openai_api.make_openai_api_call
  • Pinecone 的环境搭建、索引连接和查询请参见第三章
  • 本笔记本需要额外安装 ipython 包。Google Colab 已预装 IPython,如需安装可运行:
!pip install ipython

我们将编写的代码演示了事件驱动交互、动态内容更新以及模块化函数组织等核心概念。本节结束时,你将学会如何搭建连接 AI 功能与最终用户交互的桥梁。

image.png

构建该界面所需的主要功能组包括:

  • 初始化控件
  • 处理用户输入及选择变化
  • 处理聊天消息,包括触发功能和退出命令
  • 生成并处理AI回复
  • 动态更新用户界面
  • 保存对话历史

在从开发者角度深入代码之前,让我们保持用户视角。我们必须构建一个直观的界面,能够无缝执行图4.2中所描绘的交互流程。

image.png

界面仅包含三个控件:一个用于输入提示词的文本框,一个用于选择活跃用户的下拉列表,以及一个用于启用或禁用会话式AI代理的复选框。

下面我们逐步介绍如何搭建和运行这个交互式 GenAISys 环境。

1. 开始

程序从“以AI代理作为参与者的多用户对话”单元开始。首先导入所需模块和库,先从 IPython 开始:

from IPython.display import display, HTML, clear_output

这些功能在 Google Colab 中的作用包括:

  • displayHTML 用于展示控件、小组件、图片以及富HTML输出
  • clear_output 用于清除单元格输出内容

接着导入 Jupyter 项目管理的 ipywidgets

from ipywidgets import Dropdown, Text, Checkbox, VBox, Layout

ipywidgets 是本笔记本交互界面的核心组件,我们将使用以下控件:

  • Dropdown:用于从选项列表中选择值的下拉控件
  • Text:用于接收用户文本输入的控件
  • Checkbox:用于布尔值(选中/未选中)输入的控件
  • VBox:容器控件,用于垂直排列子控件
  • Layout:用于自定义控件样式,如宽度、高度和边距等布局属性

最后导入 json,用于存储多用户的对话历史:

import json

然后初始化所有用户的对话历史,定义第一个活跃用户,并设置会话为激活状态:

# 初始化所有用户的对话历史及活跃用户
user_histories = {"User01": [], "User02": [], "User03": []}
active_user = "User01"  # 默认用户
conversation_active = True

从一开始,我们就初始化了一个多用户协作的 GenAISys,用户既可以是人类输入的提示,也可以是系统提示。例如,“用户”也可以是来自其他系统的消息,由读取待处理消息的事件触发显示在界面中。用户列表可根据项目需要扩展,存储于变量中,或集成至任何适用的用户管理系统,包括访问权限、密码和角色管理等。

接下来,我们开始初始化这些控件本身。

2. 初始化控件

代码开始设置我们所需的 Dropdown、Text 和 Checkbox 控件,并将它们关联到事件处理函数。用户选择的 Dropdown 控件定义了对话开始时初始化的三个用户:

# 创建用于选择用户的下拉菜单控件
user_selector = Dropdown(
    options=["User01", "User02", "User03"],
    value=active_user,
    description='User:',
    layout=Layout(width='50%')
)

该控件有四个参数:

  • options 列出可选用户,项目中可根据需求扩展或接入任何用户管理系统。
  • value 确定当前活跃用户,程序初始为 User01。授权用户首次连接 GenAISys 时可自动设置。
  • description 是下拉列表显示的标签。
  • layout 设置控件显示宽度。

注意,这里构建的是 GenAISys 的核心部分,而非完整平台。目标是理解 GenAISys 的内部机制。一旦基本功能正常,我们再添加用户管理的经典层面(如姓名、角色、权限)。此时关注的是 GenAISys 灵活的核心概念,而非其在特定平台或框架中的封装方式。我们在学习如何成为生成式 AI 架构师,而非某个具体框架的操作者。

下一步是插入事件处理器,这里是一个事件监听器,监测 user_selector 的值变化。当选择了另一个用户时,会自动调用 on_user_change 函数,value 即为新用户:

user_selector.observe(on_user_change, names='value')

GenAISys 会话中用户的动态切换,标志着相较一对一聊天机器人的一大进步,为与 AI 作为协同参与者的团队协作引入了全新维度。

第二个激活的控件是输入框:

# 创建文本输入控件
input_box = Text(placeholder="Type your message here or type 'exit' to end the conversation.", layout=Layout(width='100%'))

输入框接受任意文本,宽度占满界面100%。当用户输入 “exit” 或 “quit” 时,对话结束。用户输入文本并按回车后,事件处理器开始工作:

input_box.on_submit(handle_submit)  # 绑定 on_submit 事件处理函数

on_submit 是输入控件的方法,handle_submit 是我们后续编写的回调函数。

第三个控件是用于开启或关闭 AI 会话代理的复选框:

# 创建切换 AI 代理响应的复选框
agent_checkbox = Checkbox(
    value=True,
    description='Agent',
    layout=Layout(width='20%')
)

复选框显示标签“Agent”,宽度占界面20%。当 value 为 True 时,会激活生成式 AI 代理。我们将在本章的“会话代理”部分构建该 AI 代理,它同样是事件驱动的。

至此,界面控件已准备好展示。

3. 显示界面

界面容器控件将我们定义的三个事件驱动控件组合在一个垂直布局的 VBox 中(V代表垂直,即垂直排列)。三个控件放在一个列表里:

# 显示初始界面
display(
    VBox(
        [user_selector, input_box, agent_checkbox],
        layout=Layout(
            display='flex', flex_flow='column',
            align_items='flex-start', width='100%'
        )
    )
)

布局定义为:

layout=Layout(
    display='flex', flex_flow='column',
    align_items='flex-start', width='100%'
)

该响应式界面的参数说明如下:

  • display='flex' 激活 CSS 弹性盒模型,动态布局,不需指定子项尺寸
  • flex_flow='column' 让子控件垂直排列
  • align_items='flex-start' 将控件对齐到界面容器的起始位置(左侧)
  • width='100%' 使容器占满可用空间的全部宽度

界面准备就绪,我们可以先激活其中任意一个控件。用户选择器可以在输入框和 AI 代理复选框之前运行。本例中,用户选择器默认值为 User01,AI 代理复选框默认值为 True。

这三个控件及其功能流程可以构建在任何经典的网页或软件界面中,具体取决于你的项目需求。输入框没有默认值,我们继续介绍输入控件的事件处理。

4. 输入框事件

前节描述的界面负责管理输入文本,当用户输入时触发 input_box.on_submit(handle_submit),即提交事件调用 handle_submit 函数:

# 处理输入提交的函数
def handle_submit(sender):
    user_message = sender.value
    if user_message.strip():
        sender.value = ""  # 清空输入框
        chat(user_message)

该函数执行三件事:

  1. user_message = sender.value 处理从输入控件收到的文本
  2. if user_message.strip() 判断是否有输入内容,若有则清空输入框,准备接收下一条输入
  3. 如果有内容,调用 chat(user_message) 进行后续处理

chat(user_message) 是下一步核心处理过程,是 GenAISys 的关键事件处理中心。我们接着来详细了解它。

5. chat(user_message) 函数

chat(user_message) 函数是我们事件驱动型 GenAISys 的编排器组件,设计时保持以人为核心,确保关键时刻由人类控制。一旦系统获得用户信任并经过谨慎考量,其管理的一些操作可以由系统消息触发。编排器在处理从 handle_submit(sender) 函数接收的用户消息时,包含了多项重要决策。它封装了多个选择和功能,如图4.2所示:决定是否继续对话,将对话历史追加或保存至文件,判断是否调用 AI 会话代理,以及更新用户界面显示等。

它首先继承了对话开始时初始化的全局对话状态变量 conversation_active = True(见图4.2节点1):

# 处理用户输入及可选机器人响应的函数
def chat(user_message):
    global conversation_active

接着判断多轮对话是否结束,即检查用户是否输入了退出指令(见图4.2节点6):

    if user_message.lower() in ['exit', 'quit']:

下面看看用户选择退出对话时会发生什么。

6. 如果选择“exit”

假设用户输入了 exitquit,则我们在对话开始时(图4.2节点1)设为 True 的变量 conversation_active 会被设置为 False。系统据此知道无需再更新界面显示。随后调用 clear_output 函数,等待下一轮对话时清除输出,避免界面闪烁:

    clear_output(wait=True)

退出流程继续,显示对话结束且正在保存历史的提示信息:

    display(HTML("<div style='color: red;'><strong>Conversation ended. Saving history...</strong></div>"))

退出流程最后调用对话保存函数,将所有历史记录保存至文件(见图4.2节点7):

    save_conversation_history()

因此,对话历史会在会话结束时保存,以便后续新会话或会议总结使用,具体如下(图4.2节点7):

# 保存对话历史到文件的函数
def save_conversation_history():
    filename = "conversation_history.json"  # 文件名定义
    with open(filename, 'w') as file:
        json.dump(user_histories, file, indent=4)  # 以JSON格式写入用户历史字典
    display(HTML(f"<div style='color: green;'><strong>Conversation history saved to {filename}.</strong></div>"))

现在,让我们继续了解用户选择继续对话时的流程。

7. 如果用户继续对话

如果用户输入不包含 exitquit,则多轮、多用户对话将继续。不过这个函数中有一些重大决策需要考虑:我们是否将每条用户请求追加到对话历史中?如果追加,随着上下文窗口填满,传递给 API 的 token 数量会增加,从而导致处理时间和成本上升。

第一步是将用户消息追加到我们在对话开始时初始化的对话历史中(见图4.2节点1):

# 将用户消息追加到当前活跃用户的历史中
user_histories[active_user].append(
    {"role": "user", "content": user_message}
)

在本笔记本的混合场景中,我们会将用户历史保存在内存中直到会话结束,因此每条用户输入都会附加其输入历史(见图4.2节点11)。

如果不想每条请求都追加,但又想保留完整对话记录以保持上下文,也可以在对话中间或结束时做摘要。中间摘要时,可以增加函数将摘要每次追加到用户输入中;会话结束时做摘要,则在新会话开始时附带前次会话摘要。

本笔记本实现了短期与长期记忆的混合处理。只要用户不输入 quitexit,对话就会继续。此时,chat(user_message) 函数会检查 AI 代理复选框的状态:

if agent_checkbox.value:

如图4.2节点9所示,如果复选框被选中,之前章节创建的相关功能将被激活,调用 chat_with_gpt

response = chat_with_gpt(user_histories[active_user], user_message)

当返回响应后,会将回复追加到之前描述的响应历史中:

user_histories[active_user].append(
    {"role": "assistant", "content": response}
)

此时,我们已有了一个入口级的记忆框架。程序接着调用关键函数 update_display()(见图4.2节点14),如果复选框选中,则调用 chat_with_gpt

8. 生成机器人回复

chat_with_gpt 函数整合了前几章的工作,创建了一个基于 Pinecone RAG 功能的对话式AI代理。本章“会话代理”部分将全面实现该集成。

chat_with_gpt 通过提供对话信息,协调 AI 代理,使其动态响应。当前对话的用户历史和用户消息会被传入该函数:

response = chat_with_gpt(user_histories[active_user], user_message)

响应返回后,chat(user_message) 会调用 update_display 函数更新界面显示。

9. 更新显示

update_display 函数负责刷新界面,显示更新后的对话历史和控件状态。它首先通过设置 wait=True 告诉界面等待新输出到来:

def update_display():
    clear_output(wait=True)

随后,函数筛选并显示当前活跃用户的对话历史(见图4.2节点15):

    for entry in user_histories[active_user]:  # 仅显示活跃用户的历史
        if entry['role'] == 'user':
            display(HTML(f"<div style='text-align: left; margin-left: 20px; color: blue;'><strong>{active_user}:</strong> {entry['content']}</div>"))
        elif entry['role'] == 'assistant':
            display(HTML(f"<div style='text-align: left; margin-left: 20px; color: green;'><strong>Agent:</strong> {entry['content']}</div>"))

如果对话处于激活状态,界面中的 VBox 容器及控件状态也会被显示出来:

    if conversation_active:
        display(VBox([user_selector, input_box, agent_checkbox]))  # 对话激活时保持输入框、选择器和复选框可见

输入框会被清空,AI代理复选框的状态由用户独立设置,系统会验证其当前状态。活跃用户基于用户的独立决策显示。本例中,初始化时设定的活跃用户 active_user(见节点1)保持不变。如果用户切换,user_selector 的下拉事件(见节点13)会被 observe 方法触发:

user_selector.observe(on_user_change, names='value')

此时,user_selector.observe 会独立调用更新活跃用户的函数(节点14),并首先确保 active_user 是全局变量:

def on_user_change(change):
    global active_user

然后,将新用户设为活跃用户:

    active_user = change['new']

最后调用本小节中定义的 update_display 函数刷新界面:

    update_display()

有了动态界面和事件驱动函数,接下来让我们实现由 chat_with_gpt 调用的会话代理逻辑。

会话代理

我们在第一章和第二章实现了AI会话代理,并在第三章构建了Pinecone查询功能。请打开笔记本中的“会话代理”部分,如有需要,可以先回顾相关章节再继续。本节将整合这些组件,准备我们的GenAISys会话代理支持多用户会话。

首先导入OpenAI并初始化客户端:

from openai import OpenAI
# 初始化OpenAI客户端
client = OpenAI()

接下来需要决定是否存储用户的全部对话历史,以优化上下文窗口大小,兼顾成本和清晰度:

user_memory = True  # True=保存用户消息,False=不保存

生产环境中应策略性地监控此设置。例如这里设置为True,但在RAG查询时通常不启用,以免历史上下文干扰Pinecone的相似度搜索。

定义 chat_with_gpt 函数(见图4.2节点10):

def chat_with_gpt(messages, user_message):

函数首先在输入文本中搜索关键词,判断是否触发Pinecone索引的RAG检索(参见 Query_Pinecone.ipynb 和第三章),代码先确定命名空间:

    try:
        namespace = ""
        if "Pinecone" in user_message or "RAG" in user_message:
            # 根据关键词确定命名空间
            if "Pinecone" in user_message:
                namespace = "genaisys"
            elif "RAG" in user_message:
                namespace = "data01"
            print(namespace)
        ...

若用户消息包含“Pinecone”,则查询目标为包含指令场景的 genaisys 命名空间,体现了从静态数据检索向代理式、动态决策触发指令或任务的转变;若包含“RAG”,则查询目标为包含静态数据的 data01 命名空间。Pinecone索引的查询和内容即第三章实现的内容:

        # 定义查询文本
        query_text = user_message
        # 执行查询
        query_results = get_query_results(query_text, namespace)
        # 处理并显示结果
        print("Processed query results:")
        qtext, target_id = display_results(query_results)
        print(qtext)

查询结果返回后,将用户消息附加到结果中,丰富输入上下文:

        # 运行任务
        sc_input = qtext + " " + user_message
        mrole = "system"
        mcontent = "You are an assistant who executes the tasks you are asked to do."
        user_role = "user"

消息参数及OpenAI API调用详见第一章“环境搭建”部分。OpenAI响应存储于 task_response

        task_response = openai_api.make_openai_api_call(
            sc_input, mrole, mcontent, user_role
        )
        print(task_response)

将结合Pinecone查询结果的OpenAI响应存储在 aug_output

        aug_output = namespace + ":" + task_response

若用户消息不包含触发RAG的关键词,则直接将用户请求发送给OpenAI API,响应存储于 aug_output。但系统先判断 user_memory 是否为True,并提取用户消息文本内容:

    else:
        if user_memory:
            # 从对话历史中提取所有用户消息
            user_messages_content = [
                msg["content"] for msg in messages
                if msg["role"] == "user" and "content" in msg
            ]
            # 将所有用户消息合并为单一字符串
            combined_user_messages = " ".join(user_messages_content)
            # 将当前用户消息加入合并文本
            umessage = f"{combined_user_messages} {user_message}"

此时,umessage 包含了活跃用户的完整对话历史和当前消息,生成式AI模型拥有完整的对话上下文。

对话历史的管理策略高度依赖具体用例。例如,可能会选择提取会话中所有用户的历史,也可能仅限特定用户;或团队决定整个会话中使用同一个共享用户名。最佳实践是与最终用户开展工作坊,定义并配置最适合其工作流程的对话记忆策略。

在某些情况下,也可能选择完全忽略对话历史,此时将 user_memory 设为 False,系统不再考虑之前的对话内容:

        else:
            umessage = user_message

umessage 变量准备好后直接发送给生成式AI模型:

        mrole = "system"
        mcontent = "You are an assistant who executes the tasks you are asked to do."
        user_role = "user"
        task_response = openai_api.make_openai_api_call(
            umessage, mrole, mcontent, user_role
        )
        aug_output = task_response

OpenAI API返回的响应随后被返回给 chat_with_gpt 函数(图4.2节点10):

    # 返回增强后的输出
    return aug_output

若OpenAI API调用失败,则抛出异常并返回错误信息:

    except Exception as e:
        # 发生异常时返回错误信息
        return f"An error occurred: {str(e)}"

至此,我们汇聚了前三章开发的生成式AI功能,构建了一个响应式的GenAISys界面并整合了生成式代理,形成了完整的AI控制器和编排器。接下来,让我们启动我们的GenAISys。

多用户多轮 GenAISys 会话

我们现在拥有一个响应式、事件驱动的 GenAISys,能够以多种方式执行多个任务,如图4.4所示。接下来,我们将探索利用 IPython 构建的这个 GenAISys 界面的灵活性,并整合前几章的 OpenAI 和 Pinecone 组件。

image.png

由于 GenAISys 内的功能都是事件驱动的,单个用户(无论是人类还是系统)或一组用户都可以利用该框架处理多种跨领域任务。该系统以人为中心,营造出人与生成式AI代理之间协作顺畅、无摩擦的环境。重要的是,在此框架中,人类与AI不存在竞争关系。团队成员可以在维持同事间人际关系的同时,借助 GenAISys 大幅提升绩效和生产力。这种以人为本的理念,是我在几十年为全球大型企业、中型企业及小型组织提供AI驱动自动化解决方案过程中一直倡导的。当团队将AI视为协作工具而非竞争对手时,会营造出积极氛围,快速取得成果,彰显团队合作与技术的合力效果。

深入探讨 GenAISys 框架在团队协作场景中的应用,我们可以总结出多个现实项目中常见的基本事件序列:

  • 用户选择 => 输入 => 代理启用 => RAG指令 => 生成式AI代理 => 输出
  • 用户选择 => 输入 => 代理启用 => RAG数据 => 生成式AI代理 => 输出
  • 用户选择 => 输入 => 代理启用 => 用户历史 => 生成式AI代理 => 输出
  • 用户选择 => 输入 => 代理启用 => 无用户历史 => 生成式AI代理 => 输出
  • 用户选择 => 输入 => 代理禁用 => 输出

这些基础序列构成一个序列集,记为 S:

S={a,b,c,d,e}S = \{a, b, c, d, e\}

为了实现单个用户或一组用户的目标,这些序列可以按如下方式组合:

  • {a,b}\{a, b\}:先执行基于RAG的情感分析,随后检索过去会议的情景记忆。
  • {d,e}\{d, e\}:先运行OpenAI API请求,然后为其他用户提供评论。这里的新颖之处在于,AI代理作为团队成员,有时保持沉默,让团队有时间思考其建议的想法。

这些序列可以根据具体任务和场景需要,排列成长的会话流程。由于序列可以重复出现,因此动态组合的可能性是无限的。例如,以下展示了这种灵活性的一些示例:

  • 三成员集合,如 {a,c,e}\{a, c, e\}{b,d,e}\{b, d, e\}{a,b,c}\{a, b, c\}
  • 四成员集合,如 {a,b,c,d}\{a, b, c, d\}{b,c,d,e}\{b, c, d, e\}{a,c,d,e}\{a, c, d, e\}
  • 五成员集合,如 {a,b,c,d,e}\{a, b, c, d, e\}

我们还可以将会话退出和总结加入序列中,或者重新加载保存的文件继续会话。序列之间可能重复,可能涉及不同用户和更多功能。接下来的章节中,我们将增加新功能,包括图像生成、音频、网页搜索和机器学习,进一步拓展已构建的 GenAISys 框架的应用范围。

不过本节中,我们先以两个用户进行一组简单事件序列的会话演示。随后再演示多用户和一些基础序列的场景。让我们从一个简单的事件序列开始。

两个用户的会话

在这个示例会话中,两位用户协作头脑风暴,为他们的在线旅游网站推荐吸引客户的旅行目的地。我们先启动界面会话,随后展示对话历史,最后对讨论内容进行总结。

开始会话时,请打开 Event-driven_GenAISys_framework.ipynb,并运行以下部分的代码单元:

  • 环境搭建:运行所有单元
  • 会话代理:运行单个单元
  • 在 GenAISys IPython 界面中运行界面:初始化会话

由于生成式AI模型的随机性,每次执行输出可能略有不同。同样,由于质量控制期间进行了多次运行,笔记本与印刷版章节内容也可能存在细微差异。

对话初始化完成后,让我们开始运行交互式会话。

交互式对话

默认情况下,对话由 User01 发起,界面显示输入框和已激活的代理复选框。本场景中触发的事件序列和相关函数如图4.5所示。

image.png

流程遵循以下顺序:
用户选择 => 输入 => 代理启用 => 用户历史 => 生成式AI代理 => 输出

对用户而言,这一过程无缝衔接,如图4.6所示。然而,支撑这一流畅体验的底层功能却需要精心设计和开发。

image.png

从用户的角度来看,过程非常简单。User01 在输入框中输入提示语:“法国的首都是什么?”

image.png

输出结果将显示在输入控件的上方,如下所示:

image.png

此时,即使是未经训练的用户,也能凭借一页纸提供的基础信息直观地运行 GenAISys。User01 继续提出后续问题:
User01:那里有什么值得参观的地方?

由于会话代理中的用户历史选项设置为 True,代理知道该问题是关于巴黎的,并提供了一些标志性景点的信息:

代理: 法国的首都是巴黎。巴黎有众多值得参观的景点,包括:

  1. 埃菲尔铁塔——巴黎的标志性象征,能欣赏到城市的壮丽景色。
  2. 卢浮宫——世界上最大的艺术博物馆,收藏有《蒙娜丽莎》……

此时,User02 通过下拉菜单选择了 User02,加入对话(如图4.9所示)。User02 询问西班牙的首都是哪里,代理给出回应,结果显示在输入框上方。

image.png

至此,GenAISys 核心框架已搭建完成,后续章节我们将逐步增强其功能,涵盖安全性、用户访问控制和数据隐私等方面。

User02 继续发问,询问值得参观的地方。由于用户历史选项已激活,代理能够正确回应:
User02:那里有什么值得参观的地方?
代理:
西班牙的首都是马德里。马德里有许多值得参观的景点,包括:

  1. 马德里皇宫——西班牙皇室的官方住所,现仅用于国家仪式。
  2. 普拉多博物馆……

此时,一个关键功能开始发挥作用。User02 关闭了AI代理(如图4.10所示),直接与 User01 交流,这类似于社交媒体或远程协作平台上的互动方式。

image.png

User01 非正式地回应道:“是的,这是个好主意,不过我们再仔细考虑一下。”随后,User01 通过输入 exit 结束了会话。

如图4.11所示,这一功能使我们的 GenAISys 在本书将要探讨的多种用例中提升到了一个新高度,例如以下几种配置:

  • 用户可以与 GenAISys 单独进行一对一对话
  • 团队可以协同工作,借助 AI 代理作为协作者提升绩效
  • 当人类管理者不可用时,AI 代理可扮演来自不同地点的管理者角色
  • 系统可以实时向人类用户提供信息

image.png

退出后,会话结束,对话历史会保存到 conversation_history.json 文件中:

image.png

和框架中的其他功能一样,退出行为也可以根据具体项目进行自定义。以下是一些示例:

  • 是否保存整个对话历史
  • 仅保存对话历史的部分内容
  • 保存的对话历史文件名可以包含时间戳
  • 是否选择“…跳转到下一个单元格”操作(可选)

这些都是针对具体用例需要做出的决策,不会改变 GenAISys 的整体框架,但允许高度的定制化。

本例中,团队希望展示刚刚进行的对话内容。

加载并显示对话

该功能的代码是一个标准的 IPython 显示函数,用于将 JSON 文件 conversation_history.json 转换为 Markdown 格式。首先,我们检查是否启用了对话历史显示参数和/或摘要参数:

display_conversation_history = True
summary = True

此处,对话历史和摘要功能均被激活。接下来,我们检查是否存在对话历史文件:

import json
from IPython.display import display, Markdown
import os

if display_conversation_history == True or summary == True:
    file_path = 'conversation_history.json'
    if os.path.exists(file_path):
        display_conversation_history = True
        summary = True
        print(f"The file '{file_path}' exists.")
    else:
        display_conversation_history = False
        summary = False
        print(f"The file '{file_path}' does not exist.")
        print("The conversation history will not be processed.")

如果文件存在,display_conversation_historysummary 会被设置为 True(即使之前设为 False),并打印文件存在的信息:

The file 'conversation_history.json' exists.

display_conversation_history == True,对话内容将被显示:

if display_conversation_history == True:
    file_path = 'conversation_history.json'
    with open(file_path, 'r', encoding='utf-8') as file:
        dialog = json.load(file)  # 解析JSON内容

接着定义一个将 JSON 内容格式化为 Markdown 的函数:

def format_json_as_markdown(data, level=0):
    html_output = ""
    indent = "  " * level
    ...
    return html_output

将 JSON 格式化为 Markdown 并显示:

formatted_markdown = format_json_as_markdown(dialog)
display(Markdown(formatted_markdown))

输出内容格式美观,例如:

User01:
role: user
content: What is the capital of France?

assistant:
content: The capital of France is Paris.

User02:
role: user
content: What is there to visit?

assistant:
content: The capital of Spain is Madrid. There are many attractions to visit in Madrid, including: The Royal Palace of Madrid – ...

团队已成功显示对话,但希望更进一步,总结这场包含 AI 代理参与者的在线会议内容。

加载并总结对话

我们要总结的这段对话展示了如何将AI代理融入现有人类团队,以提升生产力。在某些情况下,GenAISys 会单独完成自动化任务;在另一些情况下,GenAISys 会作为一个或多个用户的协同助手;而在组织生命中的许多关键时刻,人类团队与AI代理将能携手合作,共同做出决策。

本节中,我们将请求AI代理对对话进行总结。这个功能将在后续章节作为GenAISys的一部分集成实现。目前,我们先在显示对话后单独运行该总结功能,如图4.13所示。

image.png

代码首先加载 conversation_history.json 文件,方法与显示函数相同。然后,我们定义一个函数,将对话历史内容转换为适合 OpenAI API 的最优格式:

# 从JSON格式的对话历史构建对话字符串的函数
def construct_dialog_for_summary(conversation_history_json):
    dialog = ""
    for user, messages in conversation_history_json.items():
        dialog += f"\n{user}:\n"
        for message in messages:
            role = message["role"]
            content = message["content"]
            dialog += f"- {role}: {content}\n"
    return dialog

调用该函数构建完整的对话历史字符串:

# 从JSON历史构建完整对话
formatted_dialog = construct_dialog_for_summary(conversation_history_json)

接着准备自定义 GenAISys API 调用的完整消息内容,该函数已在环境搭建章节的 OpenAI 子部分导入:

# 用于总结对话的任务定义
mrole = "system"
mcontent = "Your task is to read this JSON formatted text and summarize it."
user_role = "user"
task = f"Read this JSON formatted text and make a very detailed summary of it with a list of actions:\n{formatted_dialog}"

最后调用 GenAISys 的 OpenAI 接口函数:

# 调用 make_openai_api_call 函数
task_response = openai_api.make_openai_api_call(
    task, mrole, mcontent, user_role
)

用 Markdown 格式显示 API 返回结果:

from IPython.display import Markdown, display
# 以Markdown格式显示任务响应
display(Markdown(task_response))

现在,一切准备就绪。若 summary==True,即可调用总结函数:

if summary == True:
    file_path = '/content/conversation_history.json'
    if os.path.exists(file_path):
        summarize_conversation(file_path)
    else:
        print(f"File '{file_path}' does not exist. Please provide a valid file path.")

注意在 Google Colab 中,/content/ 是默认目录,因此以下路径指向同一目录:

file_path = '/content/conversation_history.json'
# 或
file_path = 'conversation_history.json'

在其他环境下,可能需要使用绝对路径。

输出是对话历史的总结,包含简介和详细摘要。该摘要的提示词可调整,支持生成更短或更长的版本。我们也可以设计针对对话部分内容的提示词,或针对具体项目定制其他提示词。

本例中的输出示例如下:

JSON格式文本包含用户与助手的交互,用户询问法国和西班牙的首都,并寻求这些城市的景点推荐。以下是带操作列表的详细摘要:

User01 交互:

1. 关于法国首都的问题:

    User01 询问法国的首都。
    助手回复法国的首都是巴黎。
2. 关于巴黎景点的询问:

    User01 询问巴黎有什么值得参观的。
    助手列举了巴黎的著名景点:
    1. 埃菲尔铁塔
     - 标志性象征,必访地标。
    2. 卢浮宫
     - 最大的艺术博物馆,收藏《蒙娜丽莎》……

通过体验多种可能的任务和事件序列,我们看到了 GenAISys 提供的灵活性。接下来,让我们运行一个更复杂的多用户会话。

多用户会话

本节将运行一个技术演示会话,激活我们在前几章及本章构建的主要功能:

  • 语义分析与情感分析
  • 用于情景记忆检索的RAG
  • 无AI会话代理的对话
  • 加载、显示并总结对话历史

如果你没有中断前一次会话,只需再次运行笔记本中 GenAISys IPython 界面单元的“运行界面”部分,即可开启新对话。

如果是全新开始,请打开 Event-driven_GenAISys_framework.ipynb 并依次运行以下代码单元:

  • 环境搭建:所有单元
  • 会话代理:一个单元
  • 在 GenAISys IPython 界面运行界面:启动会话

现在,我们准备探索 GenAISys 的一些高级功能,并重点展示每条提示触发的事件和函数。会话中的第一个序列是语义分析和情感分析。

语义和情感分析

执行语义和情感分析时,需运行由 GenAISys 编排的以下序列(见图4.14):

  1. 用户选择未激活,因为会话开始时默认用户是 User01。根据用例,也可将此用户称为“主持人”。
  2. User01 输入内容,触发输入事件。
  3. 代理启用,默认复选框选中。
  4. AI会话控制器接管,解析提示,发现提示中的“Pinecone”关键词,触发指令场景命名空间的Pinecone查询,增强提示,并触发生成式AI代理。
  5. 生成式AI代理调用GPT-4o API,返回响应。
  6. 输出触发界面更新,系统准备接收新输入。

image.png

触发这一系列函数和事件的提示语如下:
“有客户说我们的旅行社挺不错,但应该增加更多活动。让我们向 Pinecone 询问一些创意。”

生成式 AI 控制器正确识别出“Pinecone”作为触发关键词,查询了指令场景命名空间,GPT-4o 由此生成了令人满意的回答:
代理:
genaisys: 为了根据客户反馈提升您的旅行社服务,…

  1. 收集数据:从旅行博客、客户评价、社交媒体和旅游论坛等多渠道收集数据。这些数据可用于训练或查询您的语义搜索模型。
  2. 生成创意:利用语义搜索结果生成潜在活动列表。例如,如果您想扩展冒险活动,搜索可能会建议滑索、攀岩或有导游的徒步旅行等……

注意,AI代理在回答开头标注了“genaisys”,表示正确查询了对应命名空间。这是一个可选的质量控制功能,开发 GenAISys 时建议实现。

图4.15中展示的第2条情感分析序列与语义分析序列相同,唯有两点差异:

    1. 用户选择事件被激活,因为此时选中的是 User02
  • 提示语中包含了情感分析的暗示

image.png

提示语中包含了“Pinecone”关键词以激活 Pinecone 查询,以及“sentiment”一词,后者是用于相似度搜索的指示词:
有客户说我们的旅行社比竞争对手差,服务应该更好。让我们问问 Pinecone 这个评论的情感倾向如何。

我们可以为所有请求的任务添加一个显式的下拉列表,部分场景中也确实会这样做。但本例中,我们依赖隐式关键词搜索。显式(例如通过下拉列表选择任务)和隐式(使用关键词或依赖提示内容)之间的平衡,应通过与用户的研讨会来决定。

本例中,选中了 Pinecone 关键词对应的 genaisys 命名空间,输出令人满意:
代理:
genaisys: 情感分析得分:0.2 解释:文本表达了对旅行社的负面情感。客户明确表示该旅行社“比我们的竞争对手差”,并建议“应该提供更好的服务”。这些陈述表明了不满和对竞争对手的负面对比,因而得分较低是合理的。

会话随后继续触发情景记忆的检索。

用于情景记忆检索的RAG

情景记忆检索序列将搜索 Pinecone 索引,以获取团队成员的个人记忆。这段记忆关联的是之前一次会议的内容,已保存并通过 upsert 写入 Pinecone 索引中的 data01 命名空间。User03 将触发如下函数和事件序列,如图4.16所示:

  1. 用户选择事件被独立触发,因为当前用户发生了变更。
  2. 输入事件触发对代理是否启用的验证(步骤3)。
  3. 代理启用后,触发会话AI代理控制器,进而根据提示中的 RAG 关键词触发 data01 命名空间中的 RAG 数据检索(步骤3.2)。
  4. 最终,生成式AI代理 GPT-4o 使用增强后的输入发起请求,并返回输出。
  5. 输出触发界面刷新,系统准备接受下一轮对话输入。

image.png

提示语明确提及了一个与团队个人经历相关的过去事件。该事件的痕迹存储为 Pinecone 索引中的情景记忆:
“CTO 前几天谈到了如何利用不同类型的记忆。他具体指的是什么?让我们用 RAG 来搜索一下。”

代理的回应令人满意:

代理:  
data01: RAG(检索增强生成)是一种结合了基于检索的方法与生成模型的框架,旨在提升AI系统的能力。当您的CTO提到利用不同类型的记忆时,可能指的是系统中信息存储、访问和利用的多种方式。以下是可能相关的“记忆”类型:

1.  **短期记忆**

我们可以看到,代理正确找到了命名空间,并且使用了提示中“您的CTO提到”的措辞,识别出这是一个与团队个人经历相关的事件,而非非个性化的语义数据。

会话随后进入头脑风暴阶段。

生成式 AI 代理进行创意构思

会话继续进行创意构思。本例中,我们希望利用生成式 AI 模型 GPT-4o 的能力来提供创意。因此,序列不会使用 Pinecone 索引来检索指令或数据,如图4.17所示:

  • 用户保持不变,
  • 输入直接进入代理启用状态(2. 输入 -> 3. 代理启用)。
  • 系统忽略 Pinecone 索引,但会考虑用户历史(3.2 用户历史)。
  • 最终,生成式 AI 代理触发调用,返回输出(4. 生成式 AI 代理)。
  • 输出触发界面更新,系统准备进入下一轮对话(5. 输出)。

image.png

提示语请求 AI 代理帮忙提供一些创意:
“但是,作为 AI 代理,你建议我们如何利用这些类型的记忆来推动我们的旅游推广活动?”

AI 代理继承了情景记忆,因为它参考了对话历史中 CTO 的思考,并基于对话历史给出建议:
“当 CTO 提到利用不同类型的记忆时,他很可能指的是在人工智能和计算领域中使用各种记忆系统来提升性能和能力的概念……
请考虑以下策略:

  1. 个性化推荐:利用短期和长期记忆分析客户偏好和过去互动,提供个性化的旅行推荐和优惠。
  2. 动态内容:利用情景记忆,根据过去客户的互动和经历定制营销内容,使促销更加相关和吸引人……”

GenAISys 为团队提供了创意。现在,团队想进一步思考这些想法。

无 AI 会话代理的对话

团队进行了简短的对话,理论上可以根据需要持续进行。此时,User01 接管并直接与团队沟通。此时 GenAISys 被用作协作式远程会议工具,如图4.18所示:

  • 由于 User01 加入,触发用户选择事件。
  • User01 取消勾选代理(Agent)控件。
  • 输入了一条消息,但提示对象是其他用户,而非 AI 代理。
  • 随后,User01 结束了对话,内容被保存。

image.png

User01 给其他人发送了一条消息:
“好了,我们到此为止,先做个总结,然后去找经理,争取推进的批准。”

图4.19显示,User01 已取消勾选 AI 代理选项,发送了消息,现在准备通过输入 exit 结束会话。

image.png

GenAISys 显示了“对话结束”消息,如图4.20所示。

image.png

该消息指示用户进入下一个单元格,以显示和总结对话内容。

加载、显示和总结对话

对话的显示和总结功能将在第5章《多模态、多功能链式推理的添加》中集成到 GenAISys 框架的功能中。

在本笔记本中,我们将按照“两个用户的会话”部分所述,继续执行后续单元格。

显示函数的输出提供了对话的 Markdown 格式文本:

…assistant  
content:  
当 CTO 提到利用不同类型的记忆时,他们很可能指的是……  
情景记忆:这涉及存储特定事件或经历的信息。在 AI 中,情景记忆可以用来回顾过去的互动或事件,以指导未来的决策……  
…为了在您的旅游推广活动中利用这些类型的记忆,考虑以下策略:  
个性化推荐:利用短期和长期记忆分析客户偏好和过去互动,提供个性化的旅行推荐和优惠。  
动态内容:利用情景记忆,根据过去客户的互动和经历定制营销内容,使促销更加相关和吸引人……  

该摘要十分有趣,为该在线旅行社提供了有价值的建议:

旅游推广的 AI 建议:  
1. 个性化推荐:利用短期和长期记忆提供个性化旅行优惠。  
2. 动态内容:利用情景记忆定制营销内容。  
3. 基于知识的洞察:利用语义记忆提供旅行建议和目的地信息。  
4. 实时互动:利用工作记忆实现与客户的实时交流。  
5. 反馈与改进:实施长期记忆系统分析反馈,提升推广活动效果。  

我们已经构建了 GenAISys 框架的基础结构,并将在后续章节持续完善。同时,我们进行了若干基础会话。现在,让我们总结本章内容,迈向更高阶段。

总结

一个复杂、事件驱动且快速变化的经济环境,需要强大的自动化能力来应对即时消费者需求所产生的数百项任务。GenAISys 通过响应式界面和生成式 AI 功能能够满足这些需求。挑战在于提供一个动态且直观的系统。无论生成式 AI 如何实现任务自动化——且自动化程度极高——最终的决策仍由人类做出。无论是线下还是线上,人与人之间在会议中的沟通是必不可少的。因此,挑战也演变为为组织提供一个支持多用户的 GenAISys。

本章首先探讨了构建多用户、多轮、多功能及 RAG 功能的高层框架。该框架包括实时记忆功能和存储于向量库中的长期知识。整体的 ChatGPT 类系统还需要响应接口和会话代理,这将在后续章节中进一步强化。

接着,我们使用 IPython 构建了一个事件驱动的 GenAISys 响应界面。该界面对终端用户无缝友好,用户只需通过三个控件操作:第一个控件用于管理用户输入,第二个控件用于切换活跃用户,第三个是用于启用或禁用基于 GPT-4o 构建的 AI 会话代理的复选框。

最后,我们运行了一个围绕在线旅行社团队的多用户、多轮 GenAISys 会话。目标包括:

  1. 为用户提供一个无缝的三控件 GenAISys 使用体验;
  2. 探索短期记忆、长期记忆、语义记忆和情景记忆的应用范围;
  3. 运行 RAG,实现指令和数据的检索;
  4. 让用户能够选择是否使用 AI 代理进行沟通。

会话结束后,我们对对话内容进行了保存和总结。

现在,我们拥有了一个可以在后续章节中配置和增强的框架,从第5章《多模态、多功能链式推理的添加》开始,将为 GenAISys 添加多模态功能和外部扩展。