维护测试监控LLM应用
维护一个生产级的 LLM 应用,我们需要做什么?
- 各种指标监控与统计:访问记录、响应时长、Token 用量、计费等等
- 调试 Prompt
- 测试/验证系统的相关评估指标
- 数据集管理(便于回归测试)
- Prompt 版本管理(便于升级/回滚)
针对以上需求,我们介绍两个生产级 LLM App 维护平台
- LangFuse: 开源 + SaaS(免费/付费),LangSmith 平替,可集成 LangChain 也可直接对接 OpenAI API;
- LangSmith: LangChain 的官方平台,SaaS 服务(免费/付费),非开源,企业版支持私有部署;
1、LangFuse
开源,支持 LangChain 集成或原生 OpenAI API 集成
官方网站:https://langfuse.com/
项目地址:https://github.com/langfuse
文档地址:https://langfuse.com/docs
API文档:https://api.reference.langfuse.com/
-
Python SDK: https://python.reference.langfuse.com/
-
JS SDK: https://js.reference.langfuse.com/
- 通过官方云服务使用:
- 注册: cloud.langfuse.com
- 创建 API Key
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_HOST="https://cloud.langfuse.com" # EU 服务器
# LANGFUSE_HOST="https://us.cloud.langfuse.com" # US 服务器
- 通过 Docker 本地部署
# Clone repository
git clone https://github.com/langfuse/langfuse.git
cd langfuse# Run server and db
docker compose up -d
# 在自己部署的系统中生成上述两个 KEY
# 并在环境变量中指定服务地址LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-.."
LANGFUSE_HOST="http://localhost:3000"
# !pip install langfuse==2.43.2
1.1、通过装饰器记录
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai # OpenAI integration
from dotenv import load_dotenv, find_dotenv_ = load_dotenv(find_dotenv())@observe()
def run():return openai.chat.completions.create(model="gpt-3.5-turbo",messages=[{"role": "user", "content": "对我说Hello, World!"}],).choices[0].message.contentprint(run())
langfuse_context.flush()
Hello, World! How are you today?
1.1.1、几个基本概念
- Trace 一般表示用户与系统的一次交互,其中记录输入、输出,也包括自定义的 metadata 比如用户名、session id 等;
- 一个 trace 内部可以包含多个子过程,这里叫 observarions;
- Observation 可以是多个类型:
- Event 是最基本的单元,用于记录一个 trace 中的每个事件;
- Span 表一个 trace 中的一个"耗时"的过程;
- Generation 是用于记录与 AI 模型交互的 span,例如:调用 embedding 模型、调用 LLM。
- Observation 可以嵌套使用。
1.1.2、observe()
装饰器的参数
def observe(self,*,name: Optional[str] = None, # Trace 或 Span 的名称,默认为函数名as_type: Optional[Literal['generation']] = None, # 将记录定义为 Observation (LLM 调用)capture_input: bool = True, # 记录输入capture_output: bool = True, # 记录输出transform_to_string: Optional[Callable[[Iterable], str]] = None # 将输出转为 string
) -> Callable[[~F], ~F]:
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai
from dotenv import load_dotenv, find_dotenv_ = load_dotenv(find_dotenv())@observe(name="HelloWorld")
def run():return openai.chat.completions.create(model="gpt-3.5-turbo",messages=[{"role": "user", "content": "对我说Hello, World!"}],).choices[0].message.contentprint(run())
langfuse_context.flush()
Hello, World! How can I assist you today?
1.1.3、通过 langfuse_context
记录 User ID、Metadata 等
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai
from dotenv import load_dotenv, find_dotenv_ = load_dotenv(find_dotenv())@observe()
def run():langfuse_context.update_current_trace(name="HelloWorld",user_id="rivers",tags=["test", "demo"])return openai.chat.completions.create(model="gpt-3.5-turbo",messages=[{"role": "user", "content": "对我说Hello, World!"}],).choices[0].message.contentprint(run())
langfuse_context.flush()
Hello, World! Nice to meet you.
1.2、通过 LangChain 的回调集成
from dotenv import load_dotenv, find_dotenv_ = load_dotenv(find_dotenv())from langchain.prompts import (ChatPromptTemplate,HumanMessagePromptTemplate,
)
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthroughmodel = ChatOpenAI(model="gpt-3.5-turbo")prompt = ChatPromptTemplate.from_messages([HumanMessagePromptTemplate.from_template("Say hello to {input}!")
])# 定义输出解析器
parser = StrOutputParser()chain = ({"input": RunnablePassthrough()}| prompt| model| parser
)from langfuse.decorators import langfuse_context, observe@observe()
def run():langfuse_context.update_current_trace(name="LangChainDemo",user_id="rivers",)# 获取当前 LangChain 回调处理器langfuse_handler = langfuse_context.get_current_langchain_handler()return chain.invoke(input="rivers", config={"callbacks": [langfuse_handler]})print(run())
langfuse_context.flush() # Langfuse 回传记录是异步的,可以通过 flush 强制更新
Hello rivers! You flow so gracefully, shaping landscapes and sustaining life. Thank you for your beauty and power.
1.3、构建一个实际应用
AGI 课堂跟课助手,根据课程内容,判断学生问题是否需要老师解答
- 判断该问题是否需要老师解答,回复’Y’或’N’
- 判断该问题是否已有同学问过
1.3.1、用 Trace 记录一个多次调用 LLM 的过程
TRACE (id: trace_id)
|
|-- SPAN: LLMCain (id: generated by Langfuse)
| |
| |-- GENERATION: OpenAI (id: generated by Langfuse)
|
|-- SPAN: LLMCain (id: generated by 'next_span_id')
| |
| |-- GENERATION: OpenAI (id: generated by Langfuse)
from dotenv import load_dotenv, find_dotenv
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI_ = load_dotenv(find_dotenv())# 构建 PromptTemplate
from langchain.prompts import PromptTemplateneed_answer = PromptTemplate.from_template("""
*********
你是AIGC课程的助教,你的工作是从学员的课堂交流中选择出需要老师回答的问题,加以整理以交给老师回答。课程内容:
{outlines}
*********
学员输入:
{user_input}
*********
如果这是一个需要老师答疑的问题,回复Y,否则回复N。
只回复Y或N,不要回复其他内容。""")check_duplicated = PromptTemplate.from_template("""
*********
已有提问列表:
[
{question_list}
]
*********
新提问:
{user_input}
*********
已有提问列表是否有和新提问类似的问题? 回复Y或N, Y表示有,N表示没有。
只回复Y或N,不要回复其他内容。""")outlines = """
LangChain
模型 I/O 封装
模型的封装
模型的输入输出
PromptTemplate
OutputParser
数据连接封装
文档加载器:Document Loaders
文档处理器
内置RAG:RetrievalQA
记忆封装:Memory
链架构:Chain/LCEL
大模型时代的软件架构:Agent
ReAct
SelfAskWithSearch
LangServe
LangChain.js
"""question_list = ["LangChain可以商用吗","LangChain开源吗",
]# 创建 chain
model = ChatOpenAI