跳到主要内容

概述

创建对话树

  • “对话树” 是 TeliChat 组织调度系统所有能力(如 “信息项”、“Python脚本/代码集成”、“知识库”、“意图” 等)的核心组件,是对话系统的“大脑”,负责整体的对话逻辑控制和执行
  • 对话树文件名(不含 “.py” 或 “.xmind” 后缀)就是对话树的名称
  • 对话树要么使用 Python 创建,要么使用 Xmind 创建,两种对话树相互之间可以调用

使用 Xmind 创建对话树

  • https://xmind.com/download 下载并安装 Xmind 软件(免费版即可)
  • 每个节点中的文本内容,以两个斜杠 “//” 开头的行都是注释行,将被忽略
  • 创建时 Xmind 的节点之间会自动形成父子关系,形成 “树”;然后可以再用 Xmind 中的 link 来增加两个节点之间的父子关系,但要注意不能形成 “环”,只能是 “有向无环图”(DAG)
  • Xmind “格式” 中 “画布” 选择 “逻辑图” 的 第 1 项
  • 每个节点的首行都是 “#...#” 或 “#...#...” 形式的节点类型,然后次行开始,可能是 “@...” 形式的节点属性定义,也可能是其它形式,具体参考各节点的章节以及 对话树节点模板.xmind(注意下载时将文件名保存为 “对话树节点模板.xmind”)
  • 注意:节点内容不应包含(全角或半角)双引号,可以用(全角或半角)单引号或方括号代替
  • 还可以参考已经画好的对话树文件 120.xmind(注意下载时将文件名保存为 “120.xmind”)

使用 Python 创建对话树

  • 请在后台管理界面的 “Python对话树” 旁边 “刷新” 按钮右侧显示的服务器目录下创建并编辑 Python 文件
  • 创建 Python 对话树文件后,在文件开头加入如下代码:
    import sys, os
    project_root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../.."))
    if project_root not in sys.path:
    sys.path.append(project_root)
    from chattree_def import *
    chattree = ChatTree()
    其中,最后一行的 chattree 变量是必需的,且不能重复定义
  • 如果还需 import 其它 Python 包或文件,可能需要使用 sys.path.append() 来添加 Python 包或文件所在路径,如果是相对路径则是相对于 TeliChatSecRun 目录
  • 然后使用 xxx_node = chattree.create_node() 接口创建对话树的节点:
    • create_node() 有两个参数,第一个参数是该节点的类型(str类型,也是 “#...#” 或 “#...#...” 形式),第二个参数是该节点的各个属性(dict类型,属性名前不用像 Xmind 对话树一样加 “@”)
    • 需要有一个变量来保存返回的新创建的节点,后继需要用该变量来进行拓扑连接
    • 注意:各个属性值中不应包含(全角或半角)双引号,可以用(全角或半角)单引号或方括号代替
  • 创建完所有节点后,使用 >> 来建立各节点之间的拓扑链接
    • 如:parent_node >> child_nodeparent_node >> child_node >> grandchild_node
    • 可以用 Python 的 list 来一次性建立多个连接,如:parent_node >> [child_node1, child_node2, child_node3][parent_node1, parent_node2, parent_node2] >> child_node
    • 注意不能形成 “环”,只能是 DAG 有向无环图
  • 在文件的最后加入如下代码:
    if __name__ == "__main__":
    chattree.render(__file__)
    这样就可以通过运行该 Python 文件来生成对话树的 HTML 交互图,方便直观了解对话树的整体拓扑结构,具体就是在 TeliChatSecRun 目录下运行:
    python3 <前面提到的后台管理界面中显示的那个目录>/对话树文件.py
    生成的 HTML 文件会保存在与该 Python 文件同目录下,文件名同名,后缀为 “.html”,然后使用浏览器打开该 HTML 文件即可预览(或者使用 VS Code 中安装的插件 Live Preview - Microsoft 来预览),更多参见 “对话树 HTML 交互图”
  • 还可以参考已经写好的对话树文件 120.py(注意下载时将文件名保存为 “120.py”);同时 “对话树例子” 中还有更多 Python 对话树可以参考
  • 服务器的 TeliChatSecRun 目录下的 chattree_def.py 文件中有对话树的相关接口定义,可以参考,但千万不要修改其中任何内容!

关键概念

知识库

代码文件

信息项

  • 类似编程语言中的 “变量”,信息项用于在对话过程中采集和存储用户提供的信息内容(也可以存储和用户输入无关的内容),并在后续的对话节点中进行引用和使用
  • 信息项分为 “用户输入信息项”、“代码定义信息项” 和 “系统信息项”,具体参见 “信息项”
  • 如何在代码中使用信息项,具体参见 “Python 脚本/函数”

意图

  • 用户在对话中的意图,会出现在对话树定义的各类节点中
  • 具体参见各类节点的章节以及 “意图”

文本输出

  • 指对话树中各类节点的输出给用户的内容,可能是直接输出的文本,也可能是对如何进行输出的描述(类似提示词)
  • 具体参见各类节点的章节以及 “文本输出”

Python 脚本/函数

  • 在 python 对话树和 xmind 对话树中都可以使用 Python 脚本/函数来实现复杂的业务逻或技术处理
  • 具体见 “Python 脚本/函数”

运行机理

拓扑结构

  • “对话树” 必须是 DAG 有向无环图
  • 有且仅有一个 “#开始#” 作为根节点,还可以有(也可以没有)一个 “#触发#” 节点作为根节点,除此之外不能有其它根节点
  • 父节点通过带箭头的连线指向子节点,或者在 Xmind 中以左边为父节点、右边为子节点(Xmind 中的 link 则也是以箭头方向区分父子节点)
  • 如果一个节点有多个父节点,则在一轮对话中可能会被遍历(执行)多次

整体执行逻辑

  • 开始执行后及每一轮对话后,“对话树” 都会停在一个交互节点(包括有明确 “系统问题” 的 “#单次交互#” 节点和没有 “系统问题” 的 “#多轮交互#” 节点)向用户输出并等用户输入后再继续执行;每次具体停在哪个交互节点,是从 “#开始#” 节点开始、按照箭头方向从左往右、从上向下的顺序进行遍历(类似一颗根节点在左边、整体向右扩展的树或DAG的深度优先搜索),遇到的第一个在其中定义的信息项还没有值的 “#单次交互#” 节点或还未执行过的 “#多轮交互#” 节点
  • 用户在上述停止的交互节点进行输入后,根据用户输入,系统会进行全局的信息项抽取,不光是当前交互节点定义的信息项,在所有 “#单次交互#” 节点定义的信息项都会被抽取信息(非当前交互节点的 “#多轮交互#” 节点定义的信息项不会被抽取)
  • 被抽取到的信息会被赋值到相关信息项(可能一个、也可能多个、也可能一个都没有),然后重新执行上述遍历以找到下一个信息项没有值的 “#单次交互#” 节点或还未执行过的 “#多轮交互#” 节点
  • 上述寻找下一个停留的交互节点的遍历过程中,经过的相关节点缺省是不会被执行的,仅这两类节点会被执行:
    • 从当前停止的交互节点到下一个停止的交互节点之间的遍历路径上的节点
    • 被当前用户输入赋予了值或更改了值的信息项所在的 “#单次交互#” 节点(可能以前在该节点停止过也可能没有)的所有子孙节点、且这些子孙节点在遇到下一个停止的交互节点前被遍历过(这种情况一般是对特定的用户输入信息项的校验和处理,被执行过一次后就不会因同样的原因再被执行)
  • 如果用户没有拒绝回答当前 “#单次交互#” 节点中的问题(或该节点的信息项有 “必需” 修饰)、也没有要求结束对话、且用户回答的内容中无法抽取出该节点的信息项的内容时,系统的执行会停留在该交互节点并(可能)再次提问
  • 影响执行顺序的除了上面信息项相关的因素,还有全局意图识别:“#触发#” 节点后的、以及具有 “主题” 属性的 “#单次交互#” 节点后的所有 “#用户意图#” 节点中的 “意图” ,都会在每次交互时进行全局意图识别,匹配时就会进入 “#触发#” 后的相应 “#用户意图#” 节点执行(执行完毕后还会返回刚才的节点继续执行)、或进入具有 “主题” 属性的 “#单次交互#” 节点后的相应 “#用户意图#” 节点执行(即主题切换 / 执行完毕后不会返回)。具体参见 “节点 - #触发#”“节点 - #单次交互#”

具体各类节点的遍历(执行)逻辑

  • 满足条件的 “#条件#” 节点后的节点才会被遍历(执行),注意条件节点的 “脚本” 或 “描述” 中的信息项的值随时都可能被改变
  • 如果 “#单次交互#” 节点后面有 “#用户意图#” 节点,则仅符合该 “#单次交互#” 节点的信息项的值的那个 “#用户意图#” 节点的后继节点会被遍历(执行),其它 “#用户意图#” 节点则会被忽略
  • 如果 “#单次交互#” 节点(没有 “必需” 信息项修饰)中的问题被用户拒绝回答(相关信息项会被赋值为 None),则该节点后继的所有子孙节点都不会被遍历(执行)
  • 按照上述逻辑,如果用户的答复改变了前面对话路径上的相关信息项的值,则对话会按照新的路径进行:如某个 “#单次交互#” 节点在 None 值和非 None 值之间切换,或后面有 “#用户意图#” 节点的 “#单次交互#” 节点转向了新的意图等
  • “#多轮交互#” 节点,其自身会循环执行,直到满足描述性的 “退出意图” 或在节点的脚本中对特定的系统信息项(“{_多轮交互退出原因_}”)进行赋值,系统才会退出循环、继续后继节点的执行(参见 “节点 - #多轮交互#”
  • 对于 “#动作#重新提问” 节点,如果其中的信息项是之前执行过的 “#单次交互#” 节点的信息项,则立刻跳到该 “#单次交互#” 节点进行重新提问;如果是还没执行过的 “#单次交互#” 节点的信息项,则可能后继再提问

Python 脚本/函数的集成

  • “对话树” 中可以在多处集成 Python 脚本/函数(参见“Python 脚本/函数”),这些脚本/函数可以调用工具或执行更加复杂的确定性逻辑;TeliChat中,大模型本身不会进行工具调用,最大限度避免 LLM “幻觉” 的负面影响
  • 对于 “#开始#” 节点的 “信息项变化触发” 属性,系统会在相应信息项的值有变化后立即自动调用相关 Python 脚本/函数;这些信息项值的变化,可能是用户输入引起的,也可能是相关 Python 脚本/函数进行的赋值引起的,具体参见 “节点 - #开始#” 中的 “信息项变化触发” 属性

用户交互及提示词内容

  • 前面提及,在 “#单次交互#” 和 “#多轮交互#” 节点中系统会与用户进行交互,而 “#提示用户#” 执行时其输出内容仅会缓存下来,在执行到下一个 “#单次交互#” 或 “#多轮交互#” 或对话结束(用户要求结束或系统主动结束)时才会(如果当前交互节点是 “单次交互” 则和“系统问题”一起)实际输出
  • 相关节点中的 “意图”“文本输出”“信息项” 等内容,都会是提示词的组成部分
    • 上述 3 类提示词组成部分中,仅有两个角色:
      • “你”(即 AI 机器人)
      • “用户”(即与 AI 对话的人)
    • 上述 3 类提示词组成部分中,省略了主语的描述,其主语一定是 “用户”
    • 上述 3 类提示词组成部分中,需要注意 “自己” 等称谓,一般指的是“用户”
  • 在任何一次交互中,系统也会根据用户输入,匹配相关的应对话术、静态参考信息(即知识库)和动态参考信息,将相关内容自动加入系统的提示词中。具体参见 “节点 - #开始#” 中的相关内容
  • “#开始#” 节点的 “背景信息”、“对话树标题”、“系统角色”、“用户角色”、“语言风格”、“固定结束语” 等属性,也会影响系统的提示词内容
  • 有时需要对相关概念进行解释澄清,可以在 “知识库” 中定义 “术语概念” 来实现,系统在构造提示词时会智能地选择并加入最终的提示词