今天,我以开源Bridgic 的链路追踪集成 (tracing) 为例,聊一聊AI时代的可观测性 (Observability) 相关的话题。
本文分成三个部分:
💾 Bridgic:一个支持动态拓扑的AI智能体框架:
点击这里下载源码 ➜ https://github.com/bitsky-tech/bridgic
可观测性技术由来已久。自从分布式的互联网技术架构发展起来并成为主流,可观测性技术也经历了多次迭代,目前已经非常完善和成熟。一般认为,可观测性又分为三个更具体的方向[1]:事件日志 (logging) 、链路追踪 (tracing) 和聚合度量 (metrics) 。
trace id串起来。相对于在充斥了各种日志的文件中用肉眼搜寻同一个请求的信息,这通常要高效得多。文本接下来的侧重点会放在链路追踪 (tracing) 上面。
为了便于后面的讨论,我们稍微复习一下链路追踪中的几个基础概念。见下图(出自谷歌的论文[2]):
随着LLM技术的发展,市场上已经涌现出一大批专门为智能体系统做可观测性的平台和开源项目。有人说,AI时代的可观测性跟以前不同了,也更重要了,因为LLM带来了非确定性的决策逻辑,系统的行为比以前更难以预测了。这么说当然没有什么大问题。
不过在技术飞速变化的当前,弄清楚什么东西变了,而什么东西没变,还是很重要的一个问题。可观测性的侧重点不同了,但思路和原则并没有发生颠覆性的变化。以前的系统,复杂度集中体现在分布式服务的调用关系上;而在AI智能体开发中,复杂度来源于如何理解智能体内部的自主行为上,也包括多智能体之间的调用关系上。这种复杂度跟分布式没有直接关系,而是跟LLM的生成行为和推理轨迹有关。
我们在集成opik、LangWatch这一类智能体可观测性平台的过程中发现,这些平台一般还是以链路追踪 (tracing) 系统为核心,这跟传统的可观测性系统在本质上是一样的。不同的是,span的概念应该在智能体的追踪中对应什么单元。在以前的可观测性系统中,span一般对应一次微服务的调用;而现在,span可能对应一次LLM调用、一次工具调用、一次subagent的handoff,或者其他关键逻辑的调用。
具体到Bridgic中,基本的执行单元被抽象成了worker,而automa作为编排容器去调度执行worker(这些概念读者可以参见我之前的另一篇文章)。很自然地,当我们每次调用一个worker的时候,就在链路追踪中自动产生一个span。这就是一种最基础的集成方式。
值得注意的是,automa本身作为worker的子类,也可以被嵌入其他automa实例中,从而通过嵌套组合的方式来达到模块化和组件化编程的目的。神奇的是,恰恰是这种概念的抽象,跟一个链路追踪系统的trace tree的概念正好对应上。最终,用一个trace id串起来的整个agentic系统的调用关系,恰好会形成一个多层级的树型结构(下一小节会看到一个具体代码例子)。
大体来看,AI时代的链路追踪,跟传统的链路追踪并没有什么本质的不同。智能体开发的场景,也很容易适配、映射到一个链路追踪系统上。当然,围绕基础的链路追踪能力,opik、LangWatch这一类平台,基本上也会涉及到评测 (evaluation) 、数据集管理 (dataset) 、prompt工程等智能体开发流程中特有的能力。这些才是AI时代可观测性中不同的那些因素(或许已经超出了「可观测性」这一概念的范畴)。另外,在智能体的可观测性度量 (metrics) 方面,对于token成本的监控,也是一个非常重要的能力。
现在,我们来小结一下前面的这些发现:
链路追踪的技术一般都有一个特点:对应用层透明。使用者只需要打开一个开关,所有tracing工作便自动开启了。Bridgic对于opik和LangWatch的集成,也是这样的效果。
下面,我们以两个具体的例子(以LangWatch为例),来展示这部分的效果(文末附代码下载)。
这个例子用于展示,使用Bridgic实现的一个嵌套组合的workflow(也可以是自主程度更高的agentic系统),是怎样在链路追踪上对应到一个trace tree上的。
先开启LangWatch的追踪:
from bridgic.traces.langwatch import start_langwatch_trace
start_langwatch_trace()
创建最内层的workflow:ThirdWorkflow。
class ThirdWorkflow(GraphAutoma):
@worker(is_start=True)
async def workflow3_step1(self, x):
return f"workflow3_step1 output"
@worker(dependencies=["workflow3_step1"])
async def workflow3_step2(self, x):
return f"workflow3_step2 output"
@worker(dependencies=["workflow3_step2"], is_output=True)
async def workflow3_step3(self, x):
return f"workflow3_step3 output"
# Create a ThirdWorkflow instance.
workflow3 = ThirdWorkflow()
创建中间一层的workflow:SecondWorkflow。前面创建的workflow3实例,被嵌入到SecondWorkflow实例的第2个worker的位置。
class SecondWorkflow(GraphAutoma):
...
workflow2 = SecondWorkflow()
@workflow2.worker(is_start=True)
async def workflow2_step1(x):
return f"workflow2_step1 output"
workflow2.add_worker(
key="workflow2_step2",
worker=workflow3,
dependencies=["workflow2_step1"],
)
@workflow2.worker(dependencies=["workflow2_step2"], is_output=True)
async def workflow2_step3(x):
return f"workflow2_step3 output"
创建顶层的workflow:TopWorkflow。它内部包含两个worker,其中第1个worker是前面创建的workflow2。
top_workflow = TopWorkflow()
top_workflow.add_worker(
key="top_workflow_step1",
worker=workflow2,
is_start=True
)
@top_workflow.worker(dependencies=["top_workflow_step1"], is_output=True)
async def top_workflow_step2(x):
return f"top_workflow_step2 output"
最后,执行top_workflow:
await top_workflow.arun(x="top_workflow input")
在LangWatch平台上,显示出来的trace详情和时序图,分别如下面两个图所示:
还是先开启LangWatch的追踪,并创建LLM:
from bridgic.llms.openai import OpenAILlm, OpenAIConfiguration
from bridgic.traces.langwatch import start_langwatch_trace
start_langwatch_trace()
llm = OpenAILlm(
api_base=_api_base,
api_key=_api_key,
configuration=OpenAIConfiguration(model=_model_name),
timeout=20,
)
使用ReActAutoma,创建一个旅行规划的Agent:
travel_planner_agent = ReActAutoma(
llm=llm,
system_prompt="You are a travel planner. You are given a city and a number of days. You need to plan a trip to the city for the given number of days.",
tools=[get_weather, get_flight_price, get_hotel_price],
)
执行它:
await travel_planner_agent.arun(
user_msg="Plan a 3-day trip to Tokyo. Check the weather forecast, estimate the flight price from San Francisco, and the hotel cost for 3 nights."
)
在LangWatch平台上,显示出来的trace详情和时序图,分别如下面两个图所示:
从上面两图中,我们可以看到ReActAutoma内部对于三个工具的调用(get_weather、get_flight_price、get_hotel_price)。这三个工具其实是并发调用的,它们被调用的起始时间几乎是同一时刻。但是,这种并发关系在上面的时序图中并没有体现出来。这说明,一些tracing系统对于并发执行关系的可视化,还没能够处理得特别精细。
Bridgic源码地址 ➜ https://github.com/bitsky-tech/bridgic
Bridgic文档地址 ➜ https://docs.bridgic.ai/
本文第1个例子代码 ➜ https://github.com/bitsky-tech/bridgic-examples/blob/main/trace/workflow_nested_tracing.py
本文第2个例子代码 ➜ https://github.com/bitsky-tech/bridgic-examples/blob/main/trace/react_tracing.py
Bridgic可观测性的教程 ➜ https://docs.bridgic.ai/latest/tutorials/items/observability/
一句话,Bridgic是一个支持动态拓扑、强调组件化编程的开源AI智能体框架,也是学习AI编程、学习Python编程和异步编程的一份参考代码,很值得一读。觉得好的朋友可以给个star ^-^
我新建了一个“Bridgic开源技术交流群”,后面会在群里发布项目的开发进展及计划,并讨论相关技术。感兴趣的朋友可以扫描下面的二维码进群。如果二维码过期,请加微信ID: zhtielei,备注“来自Bridgic社区”。
(正文完)
其它精选文章: