Parlant:一款优雅可控的Agent框架

AI智能体(Agent)开发框架与平台可谓百家争鸣,各有所长。但一个不争的事实是,大部分在底层设计上是趋同的。不过今天我们将要体验的这款开源免费框架 - Parlant,或许会颠覆你的传统认知:

一款旨在最大程度消除LLM带来的不确定性,以帮助你“驯服”智能体的框架。

Build compliant AI chat agents, in minutes.
The production-ready engine for Al chat agents that generate aligned responses and follow instructions-even as you scale complexity.

接下来让我们一起走进这款独特的框架:

  • 企业应用Agent的最大“拦路虎”
  • 构建对话式Agent:主流框架的方法
  • 构建对话式Agent:Parlant的方法
  • 深度实践:迭代构建一个模拟10086客服助手
  • 总结、及与LangGraph对比

这不一定是你见过最强大的,但很可能是最特别的、甚至“有趣”的Agent框架。

01 企业应用Agent的最大"拦路虎 "

很多人都认识到:尽管大模型(LLM)有着强大的自然语言理解与处理能力,但它的不确定性是限制它在面向客户与关键业务的企业场景中得以广泛应用的最大障碍 。传统的Agent通常依赖于大量这样的复杂LLM提示:

prompt="
你是一名智能客服助手,目标是高效、准确地帮助客户解决问题。  
你具备以下能力: 
...
### 工作方式(比如ReAct思考模式):
...
### 确保遵循如下业务规则和输出要求:
...
### 可用的工具列表:
... 

"

遗憾的是,LLM无法确保绝对的指令遵循:有时候会产生偏差或遗忘,有时候会“幻觉”,特别是在一些长任务或者长对话场景中,随着上下文的“膨胀”,它的输出更加“不可预测”,并可能直接导致Agent工作的失败。

为了优化这个问题,这些方法已经被不断尝试:

  • 流程层面:Workflow/状态机 (约束智能体流程)、HITL(人类检查)
  • 节点层面:规则辅助(减少LLM依赖)、样本(让LLM参考)
  • 输出层面:结构化输出(提高LLM输出的一致性)
  • 模型层面:微调(适应特定领域)、R4G (给LLM补充知识)

它们在很大程度让Agent行为更可控,但情况仍然复杂:

  • 增加提示样本:样本只代表少数,覆盖率有限
  • 微调:限定在特定领域,换了环境可能会更差
  • RAG:给模型增加了知识,但无法其思考方式
  • 结构化输出:形式可控,但内容可能是“幻觉”
  • 工作流:灵活性低,且节点可能仍然依赖LLM

所以,如何让Agent不偏离预期行为,提高行为的一致性和确定性,一直是各种开发框架与应用的努力方向(甚至也是Gen-AI的最大“黑点”)。

02 构建对话式Agent: 主流框架的方法

假如我们需要构建一个典型的对话式Agent(如智能客服),典型的方法有:

借助增强型LLM的Agent

借助提示工程(参考第一节的例子)、函数调用(Function Calling)与外部工具(RAG管道、企业API)构建的ReAct范式的Agent:

这种方法的最大问题是过度依赖于Prompt与LLM的理解、遵循与推理能力,随着上下文的增大,发生偏移和幻觉的概率越大,而且你需要在每次推理中把所有希望LLM遵循的原则告诉它。如果我们把Agent比作是一个数字员工,那么这种方法就是:

给员工一本厚厚的手册,让他去服务客户,偶尔犯错是难免的。

基于工作流定义的Agent

借助如LangGraph、AutoGen等框架定义的流程式Agent,按照预定义的过程与客户交互(ReActAgent也可以看做是工作流的特例)。


这种Agent的缺点是比较僵化(虽然在局部仍然会体现LLM的智能),特别是AI对话式场景下,很难预测客户的交互行为时,容易陷入"不知所措”或者人工”智障“的状态,它更适合复杂任务型的Agent。用上面的比喻来说,这种方式是:

给员工制定严格的行动步骤,但客户如果”不走寻常路“,就容易出错。

那么Parlant是如何做的呢?

03 构建对话式Agent: Parlant的方法

Parlant专注于对话式Agent的场景,并设计了一种独特的控制Agent与用户之间交互的新方法,被称之为ABM(Agent Behavior Model,用户行为建模)。

简单的说,就是给Agent指定一套完整的、结构化的、自然语言描述的业务准则,包括Agent的目标、行动指南(Guideline)、对话流程(Journey)、标准化回复(Canned Response)、词汇表(Glossary)等,并通过框架来强制这些准则的应用与执行。

以一个典型的行动指南(Guildeline)为例:

await agent.create_guideline(     
    condition="客户询问话费余额",     
    action="使用query_balance工具查询并告知客户当前余额",     
    tools=[query_balance]     
)

注意,你无需编排工作流,也无需用提示词让LLM识别客户意图,你需要做的只是很自然的设定这个准则,Parlant会确保这个准则被执行。

那么Parlant是如何最大可能的确保准则被遵循的呢?尽管内部过程较复杂,并且依赖于一种ARQ(注意推理查询)的创新提示技术,但可以总结成两点:

  1. Parlant引擎会动态根据上下文来评估与加载本次交互相关的规则,并基于这些规则来执行操作与合成回复。
  2. 在Agent输出回复给客户之前对其进行监督,以最大程度的确保Agent正确遵循了必要的准则后给予放行。

用之前的比喻来说,Parlant的方式就是:

在员工服务客户的过程中,有权威会根据情景持续指导你并监督执行。

对比主流框架的方法,Parlant方法的好处是:

  • 降低LLM的认知负担(每次只加载相关准则)
  • 非常适合处理边缘场景(添加针对性准则)
  • 可以为每一次Agent行为提供可解释性(毕竟有”权威“盯着)

下面我们用实例来认识Parlant中的核心模块。

04 实战:构建一个模拟的10086客服助手

在这个案例中,我们一步步的借助Parlant来创建一个模拟10086客服的Agent,你将可以深入认识与体会ABM建模是如何帮助创建一个易于理解、更可控的企业级对话式Agent。

【步骤1:准备工作】

安装Parlant的开发SDK(Python3.10以上):

pip install parlant

例子中使用了OpenAI接口,因此你至少需要设置环境变量OPENAI_API_KEY。(如果是借助第三方API平台,需要设置OPENAI_BASE_URL,但默认Parlant SDK中不支持传入OPENAI_BASE_URL,方法请参考本文源码中的说明)。

【步骤2:创建智能体(Agent)】

Parlant的Agent是一个定制化的 AI 角色,代表你的“服务员工”。你可以使用 SDK 创建并配置多个 Agent,每个 Agent 有独立的身份、背景和行为特征;每个 Agent 都可以拥有自己的行为模型和对话历史,相互隔离。

通过几行代码创建一个Agent:

async def main() -> None:     
    async with p.Server(     
        nlp_service=p.NLPServices.openai     
    ) as server:     
        agent = await server.create_agent(     
            name="10086客服小助手",     
            description="专业友好的电信运营商客户服务代表,提供话费查询、套餐变更等服务。")         
    
if __name__ == "__main__":     
    import asyncio     
    asyncio.run(main())

Parlant的Agent特点是运行即服务:会自动启动一个Agent Server,你可以通过API与Server中的多个Agent对话。Parlant提供了一个测试的前端UI,访问http://localhost:8800,就可以体验这个原始版Agent:

user:Hello,你会做什么呢?
10086客服小助手:你好! 我可以帮助你查询话费、变更套餐, 或者解答其他与电信服务相关的问题。请问你需要什么帮助呢?

【步骤3:添加简单的指南(guideline)】

指南(guideline,也叫准则),是 Parlant 建模的核心单元。每条准则由“何时触发” (condition) 和“做什么” (action) 两部分组成 ,用简单自然语言编写。

现在来给Agent添加简单的两条准则:

await agent.create_guideline(     
    condition="客户进行日常闲聊",     
    action="友好回应,但适当引导到业务服务话题。"     
)  
await agent.create_guideline(     
    condition="当客户明确表达出对服务不满意或有投诉、转网的意图",     
    action="首先表示歉意,并表示将会让客户经理联系客户,了解客户需求并提供帮助。"     
)

看下测试效果:

user:你好,我想注销我的电话号码。
10086客服小助手:你好! 很抱歉听到您想注销电话号码。我们可以安排客户经理与您联系,了解您的需求并提供帮助。请问您方便提供联系方式吗?

可以看到,Agent完美的遵循了服务准则。

Parlant 会动态筛选当前情景下最相关的准则,并强制遵循,从而保证 Agent 行为始终符合业务预期(如果准则之前存在冲突或者依赖关系,Parlant支持定义准则间的关系)。

【步骤4:添加带工具(Tools)的指南】

现在我们来让小助手具有“查询话费”的能力,这就需要用到工具(Tool)这个模块。Parlant中通过@p.tool这个装饰器定义工具:

@p.tool
async def query_balance(context: p.ToolContext, phone_number: str) -> p.ToolResult:  
    #....省略模拟的API调用...  
    result = f"您的话费余额为{info['balance']:.2f}元,下次缴费日期:{info['due_date']}"     
    return p.ToolResult(result)

现在来添加一条需要”使用工具“的业务准则:

await agent.create_guideline(     
    condition="仅当客户明确要求查询自己的话费余额",     
    action="使用query_balance工具查询并告知客户当前余额",     
    tools=[query_balance]     
)

在准则中关联工具后,只有当规则触发时,Agent 才会调用相应工具并将结果用于生成回答 。这种设计让工具调用变得可控且上下文敏感,避免了模型“盲目调用”的问题。

测试新业务准则的执行:

user:你好,我想查询下我的话费还剩多少?
10086客服小助手:你好! 请提供您的手机号码,以便我为您查询话费余额。
user:号码是13812345678
10086客服小助手:好的,让我为您查询一下。 您的话费余额为85.60元,下次充值日期为2024-12-15。

【步骤5:注册客户(Customer)并获取信息】

上面的测试中可以看到Agent会要求输入电话号码以查询话费,但是在实际场景中,客户在登录后就已经可以获得其信息(比如手机号码),并且应该在后续的对话上下文中自动带入。在Parlant中,你可以通过Customer这个模块来完成:

在完成登录后,创建一个客户(实际应用中通常是客户端来创建):

customer = await server.create_customer(name="user",metadata={"phone_number": "13812345678"})

然后你就可以在工具中直接通过上下文获得电话号码,而无需客户提供:

...  
server = p.ToolContextAccessor(context).server  
if customer := await server.find_customer(id=context.customer_id):     
    phone_number = customer.metadata.get("phone_number")

现在你会发现AI可以直接调用工具帮你查询话费,而无需提供号码。

【步骤6:实现多步骤复杂交互(Journey)】

Journey(旅程)是Parlant中相对复杂的模块,你可以理解成是Parlant的工作流。它用于定义Agent复杂流程的对话走向,通常对应到业务场景中一些多步骤的引导与交互过程,比如行程预定、业务变更等。

一个 Journey 包含标题、触发条件和状态转换图(状态与条件/动作的有向图)。Parlant 允许Agent灵活地按照旅程的状态图进行对话,但也可以跳过、中途返回或提前进入某些状态,从而在保证收集必要信息的同时,提升用户体验 。

我们用Journey来实现“话费套餐变更”这个功能。这个对话“旅程”如下:

【图片标记:此处应有套餐变更流程示意图,包含开始、客户要求变更套餐、查询可用套餐query_available_plans、展示套餐列表询问客户选择、客户选择套餐、确认是否变更、客户决定(是/否)、执行套餐变更change_plan、告知变更结果、结束等节点及流向】

将其转换成Parlant的SDK实现。首先创建两个模拟工具,分别用来查询可用套餐与执行变更套餐:

@p.tool
async def query_available_plans(context: p.ToolContext) -> p.ToolResult:...

@p.tool
async def change_plan(context: p.ToolContext, new_plan_id: PlanID) -> p.ToolResult:...

接着来定义这个旅程:

async def create_plan_change_journey(agent: p.Agent) -> p.Journey:     
    """创建套餐变更旅程"""     
    journey = await agent.create_journey(     
        title="话费套餐变更",     
        description="帮客户找到合适的话费套餐并完成变更",     
        conditions=["仅当客户明确表示需要变更话费套餐"],     
    )     
    t0 = await journey.initial_state.transition_to(tool_state=query_available_plans)     
    t1 = await t0.target.transition_to(chat_state="列出可以变更的套餐,然后询问客户需要变更到哪个套餐")     
    t2 = await t1.target.transition_to(chat_state="询问客户是否确认本次变更",condition="客户选择变更到某一个新的业务套餐")     
    t31 = await t2.target.transition_to(tool_state=change_plan,condition="客户确认变更套餐",tool_instruction="new_plan_id参数值从可用的套餐中提取")     
    await t2.target.transition_to(state=t0.target,condition="客户表示还需要再考虑一下")     
    t4 = await t31.target.transition_to(chat_state="根据变更的结果告知客户变更成功或者失败",condition="套餐变更结束")     
    await t4.target.transition_to(state=p.END_JOURNEY)     
    await journey.create_guideline(     
        condition="客户明确表示需要深入了解某个套餐的详细信息",     
        action="查询某个套餐的详细信息并告知客户,plan_id从客户对话中提取",     
        tools=[get_plan_details],     
    )     
    return journey

这个定义方式并不难理解,就是用代码来描述:在什么条件下,从A状态跳转到B状态,状态可以是对话状态(chat_state)或工具状态(tool_state)。

另外,一个Journey也可以定义属于它的准则(Guideline)。比如这里针对客户可能在任何时候提出想”深入了解某个套餐“而添加了一条准则。

现在测试这个Journey的基本流程:

10086客服小助手:
PLAN004-青春套餐38元: 通话200分钟, 流量8GB, 短信50条
PLAN005-优享套餐188元:通话无限,流量无限, 短信无限
请问您想变更到哪个套餐呢?

user:
变更到188元这个套餐

10086客服小助手:
好的,让我为您处理一下。
请您确认一下,您想要变更到的套餐是188元的优享套餐吗?

但是,Parlant的Journey与其他工作流不一样的是:

它不是一种严格遵循的固定流程,只是一种指导性的框架流程。Parlant框架会在每次交互时智能的来判断应该激活的流程状态,即使你并没有定义这样的“跳转”。这给Agent赋予了极大的自适应能力。

比如我在这个”旅程“中“反复横跳”:

user:
算了,还是不变了吧

10086客服小助手:
明白了,稍等一下。 很抱歉让您感到不满意。我会让客户经理联系您,了解您的需求并提供帮助。请您稍等一下。

user:算了还是帮我做个变更吧,换个套餐

10086客服小助手:
明白了,请稍等。 很抱歉让您感到不满。请您告诉我想要更改到哪个套餐?

user:变更到188元那个套餐

10086客服小助手:
很抱歉让您感到不满意。您选择的套餐是188元的优享套餐, 具体信息 如下:
·月费:188元
·通话:无限制
·流量:无限制
·短信:无限制
·特殊服务:免费接听, VIP客户服务, 全国通用, 5G网络, 免费热点, 国际漫游优待 ·超出资费:无超出资费使用
请问您确认要变更到这个套餐吗?

你会发现这里的互动并没有严格遵循流程走向。但这才是真实的应用场景:你无法预测客户真实的交互行为。

【步骤7:利用变量减少工具调用(Variable)】

在很多低代码平台中也存在变量(Variable) 的概念:用来标识一条在上下文中经常会用到的特定信息,并且可以按需更改和刷新。

在测试上面的例子时我发现Agent经常会通过query_available_plans来获取可用的套餐信息,于是决定把这个信息放到变量里,并设定每小时刷新一次:

# 添加变量     
available_plans = await agent.create_variable(     
    name="可用话费套餐",     
    description="客户可以选择的套餐列表",     
    tool=query_available_plans,     
    freshness_rules='0 * * * *'     
)

这里指定通过Tool来刷新available_plans这个变量,并且刷新规则用freshness_rules来指定(遵循crontab表达式)。

变量会由框架动态评估是否带入到每次对话的上下文,以及是否需要刷新。 默认情况下(不指定freshness_rules),变量会在每次交互时自动刷新。

现在测试Agent并观察后台,你会发现Agent只会在第一次需要时调用工具初始化这个变量,而后续的交互会自动带入该信息。

【步骤8:设定标准化回复(Canned Response)】

在企业服务场景下,有一些标准的“话术”,以体现服务的标准化和一致性。尽管Parlant已经尽量让Agent更加受控,但在回复消息上依赖于LLM仍然存在较大的不确定性,此时你可以借助标准化回复这个模块。当触发相应条件时直接调用,从而 消除幻觉,并确保输出的统一 。

注意Parlant的标准化回复的特点:

  • 基于模板定义,可以包含如变量、工具响应中的动态字段
  • 非简单复制使用,而是会动态选择合适的模板,并智能合成消息

我们让Agent在"闲聊”这个准则下使用预设回复模板:

#欢迎
welcome = await agent.create_canned_response(  
   template="你好!{{std.customer.name}}, 很高兴为您服务。请问有什么可以帮您?")
...
await agent.create_guideline(     
    condition="客户进行日常闲聊",     
    action="友好回应,但适当引导到业务服务话题。",    
    canned_responses=[welcome]
)

看下测试效果(Parlant的交互默认会有一个前导响应,也可以设定):

user:
你好啊

10086客服小助手:
你好! 很高兴为您服务。请问有什么可以帮您?

【步骤9:添加特定术语(Glossary)】

词汇表,顾名思义它就相当于一个Agent可以参考的“字典”。 通常用来定义你的领域特定术语,用来帮助Agent理解其含义;也可以用回答一些固定的问题。

比如在上面的情境中,如果客户要求变更到“至尊套餐”,那么你就需要让Agent知道至尊套餐是代表哪一个话费套餐。

让我们添加两个术语:

# 术语
await agent.create_term(     
    name="营业时间",     
    description="营业时间是周一至周五,上午9点到下午6点。",
)
await agent.create_term(     
    name="至尊套餐",     
    description="至尊套餐是最高级别的188元套餐",
)

看看测试效果:

user:
帮我变更到至尊套餐

10086客服小助手:
好的,让我为您处理一下。 请确认您要变更到至尊套餐, 套餐费用为188元, 包含无限通话和无限流量。

您确认吗?

【步骤10:将Agent集成到前端】

在Agent构建完成并且测试通过后,你可能需要将Agent集成到你的前端应用中,让客户可以通过UI与你的Agent交互。这有两种方式:

  1. 借助Parlant提供的REST API;
  2. 借助官方提供的parlant-chat-react插件 (需要React JS框架),它提供了一个完整的对话界面,简单配置即可连接到你的Agent。最简单的例子如下:
import React from 'react';
import ParlantChatbox from 'parlant-chat-react';

function App() {  
  return (     
    <div>     
      <h1>My Application</h1>     
      <ParlantChatbox     
        server="http://localhost:8800"    
        agentId="your-agent-id"     
      </div>  
  );
}

export default App;

05 总结、及与LangGraph的对比

以上通过迭代的方式完成了一个简单的对话式AI助手。

在整个过程中,我们切身体验到的Parlant的一些独特之处:

  • 声明式的自然语言规则定义,并通过创新机制来确保与监督执行
  • 能够高度灵活的适应真实的客户对话场景(非死板的流程遵循)
  • 高度模块化设计,ABM的每个模块都功能清晰,简洁易用
  • 针对高标准与合规要求的企业级设计。如规则遵循、标准化回复等

而一些目前的不足主要在于:

  • 内部对规则执行的过程较复杂,Tokens成本略高
  • 毕竟还要依赖LLM,还是存在推理失误现象(如调用多余工具)
  • 不需要直接设计Prompt,但自然语言的描述还是需要优化调试
  • 当出现内部推理不精确时,可能导致反复尝试,响应较慢

整体来说,Parlant引入了“行为建模”这一令人眼前一亮的全新范式,为智能体(主要是对话性Agent)的构建注入了企业级应用更需要的可控性与可解释性,并采用了一种完全不同于其他Agent框架的”优雅“方式,把Agent开发从繁琐的“提示词”+“工作流微调”中解放出来,用一种更轻松的方式来定义Agent行为,同时又不失强大与灵活,非常值得持续关注。

最后简单对比下Parlant与LangGraph的定位:

维度 Parlant LangGraph
定位 专注于单一客户交互代理,保证行为一致性和业务规则合规性 面向通用复杂工作流与多智能体系统,强调长时、可扩展编排
适合场景 客服、金融合规、需要透明可审计的对话型应用 研发任务分解、审批流、数据处理、多Agent协同、复杂业务自动化
设计理念 行为建模(Guidelines+Journeys),声明式规则驱动 状态化编排(Graph+Engine),开发者自定义节点与状态流
流程编排 内置Journey状态机,支持跳过/回溯,日志记录事件流 图结构,支持条件边、循环、检查点、耐久执行和时间旅行
可解释性 Guideline规则触发机制,减少LLM随机性;Session日志可追溯 检查点+Replay+Time Travel,支持流程回放与分支试验,调试能力强
社区活跃度 GitHub~10.6k stars,文档详尽,聚焦对话场景,规模中等 GitHub ~18k stars,LangChain生态一部分,大厂案例多,更新活跃度高

Parlant适合构建高度可控、透明、合规的对话智能体(客服、金融、政务服务等);LangGraph更适合需要复杂工作流、多Agent协同、长时执行的任务场景。

本文源代码:https://github.com/dequal/parlant-demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容