Langfuse 本地 AI 调试台怎么给团队验收?我用 cpolar 开了一个短时观测入口
Langfuse 本地 AI 调试台怎么给团队验收?我用 cpolar 开了一个短时观测入口
AI Demo 跑通以后,最容易吵起来的不是模型能不能回答,而是“这次请求到底发生了什么”。
产品看到一句回答,测试看到一个失败用例,后端看到接口超时,算法同事想看 prompt 和 token。只靠终端日志,大家很快就开始互相截图、互相猜。
我现在的做法是:本地用 Docker 起一套 Langfuse,把测试请求打成 trace;验收时只用 cpolar 给 Langfuse Web 面板开一个临时 HTTPS 地址。团队看同一组脱敏 trace,确认 prompt、响应、耗时、token 和错误日志。验收结束,关掉 cpolar,公网入口立刻回收。
这篇不写大而全的 AI 平台搭建,只把一个闭环跑通:Langfuse 本地部署、最小 trace 上报、团队远程看面板、cpolar 短时 HTTPS 验收、安全边界和排错。
适用场景
这套流程用在 AI Demo 已经能调用模型,但还没进入正式环境的阶段,效果很直接。
比如你本机跑了一个客服问答、RAG 检索、提示词改写、Agent 工具调用,接口已经能返回结果。问题是团队验收时不能只看“最终回答”,还要看这次调用用了哪个 prompt、输入输出是什么、耗时卡在哪里、失败原因是模型报错还是业务代码报错。
可以用在这些场景里:
- 本地 AI Demo 需要产品、测试、后端一起验收调用链
- Prompt 改动后,要把新旧 trace 放在面板里对照
- 测试同事要复现异常输入、超时、空回复、JSON 解析失败
- 后端同事要确认 token 用量、延迟、错误日志是否完整
- 公司内网机器能跑 Docker,但外部协作成员打不开本地面板
- 验收数据已脱敏,只展示测试用户、测试 prompt、测试输出
重点不是把 Langfuse 长期挂公网。
重点是:本地观测面板只在验收窗口短时间开放,验收完马上关。
最终效果
跑完后,你会得到三样东西:
- Langfuse 本地面板:
http://localhost:3000 - 一条能在 Langfuse 里看到的测试 trace
- cpolar 临时 HTTPS 地址:形如
https://xxxx.cpolar.top
团队验收链路是这样:
团队成员浏览器 | | HTTPS v cpolar 临时公网地址 | | 隧道转发 v 本机 Langfuse Web 面板 :3000 | v 查看脱敏 trace / prompt / latency / token / error你在本机继续跑 AI Demo 和 Langfuse。团队成员打开 cpolar 地址,登录 Langfuse 面板后查看同一批 trace。
验收会议里不用再贴十几张日志截图。大家直接在 Langfuse 里点开一次请求,看输入、输出、耗时、metadata、level、status message,问题归因会快很多。
环境准备
本文命令以 macOS / Linux 为例。Windows 用户放到 WSL2 里执行也能跑。
需要准备:
- Docker
- Docker Compose v2
- Git
- Python 3.10+
- cpolar 客户端和 authtoken
先检查 Docker 和 Git:
docker --version docker compose version git --version检查 Python:
python3 --version准备工作目录:
mkdir -p ~/ai-observe-demo cd ~/ai-observe-demo如果你的机器内存比较紧,先关掉不用的容器。Langfuse v3 的 Docker Compose 会带 Web、Worker、Postgres、ClickHouse、Redis、MinIO 等组件,本地调试能跑,但别拿这套单机 Compose 当高可用生产方案。
Docker 启动 Langfuse
Langfuse 官方仓库已经提供 Docker Compose 文件。本地试跑最省事的方式就是直接拉仓库。
cd ~/ai-observe-demo git clone https://github.com/langfuse/langfuse.git cd langfuse启动前先生成几组本地 secret。下面这段命令只是在终端里生成随机字符串,方便你替换配置里的默认值:
openssl rand -hex 32 openssl rand -hex 32 openssl rand -hex 32打开docker-compose.yml,把里面明显的默认密钥替换掉。不同版本字段会有变化,看到NEXTAUTH_SECRET、SALT、ENCRYPTION_KEY、数据库密码、ClickHouse 密码这类字段,都换成自己的值。
本地教程里偷懒不换密钥,短时间也许看不出问题,但一旦你后面用 cpolar 暴露面板,默认密钥就是很糟糕的习惯。
启动 Langfuse:
docker compose up -d查看容器状态:
docker compose ps看 Web 容器日志:
docker compose logs -f langfuse-web等日志里出现服务 ready 相关信息后,打开:
http://localhost:3000第一次进入面板,按页面提示注册本地管理员账号。然后创建一个项目,比如:
Project name: local-ai-acceptance进入项目后,在项目设置里创建 API Keys。你会拿到:
LANGFUSE_PUBLIC_KEY=pk-lf-xxxx LANGFUSE_SECRET_KEY=sk-lf-xxxx LANGFUSE_HOST=http://localhost:3000这三个值后面会给最小示例使用。
接入最小示例:先生成一条 trace
为了让教程可以直接跑,我这里不用真实模型 API。先写一个“假 LLM 调用”,重点验证 Langfuse trace 能不能上报。
新建一个目录:
cd ~/ai-observe-demo mkdir -p langfuse-client-demo cd langfuse-client-demo python3 -m venv .venv source .venv/bin/activate pip install -U langfuse写环境变量文件:
cat > .env <<'EOF' LANGFUSE_PUBLIC_KEY=替换成你的_public_key LANGFUSE_SECRET_KEY=替换成你的_secret_key LANGFUSE_HOST=http://localhost:3000 EOF让 shell 读取它:
set -a source .env set +a写一个最小脚本demo_trace.py:
cat > demo_trace.py <<'PY' import os import time from langfuse import Langfuse langfuse = Langfuse( public_key=os.environ["LANGFUSE_PUBLIC_KEY"], secret_key=os.environ["LANGFUSE_SECRET_KEY"], host=os.environ.get("LANGFUSE_HOST", "http://localhost:3000"), ) trace = langfuse.trace( name="local-ai-acceptance-demo", user_id="test-user-001", session_id="acceptance-session-20260714", input={ "question": "用户问:订单 12345 为什么还没发货?", "env": "local-demo", }, metadata={ "app": "customer-service-demo", "git_branch": "feature/langfuse-trace", "tester": "qa-local", }, tags=["local", "acceptance", "cpolar-demo"], ) start = time.time() prompt = """你是客服助手。请根据订单状态给出简短回复。 订单号:12345 订单状态:仓库已打包,等待快递揽收 """ # 这里模拟一次 LLM 返回,先不依赖任何外部模型服务 answer = "订单 12345 已完成打包,正在等待快递揽收。揽收后会自动更新物流信息。" time.sleep(0.8) latency_ms = int((time.time() - start) * 1000) trace.generation( name="mock-llm-call", model="mock-gpt-local", input=prompt, output=answer, usage={ "input": 42, "output": 31, "total": 73, }, metadata={ "latency_ms": latency_ms, "temperature": 0.2, }, ) trace.update( output={"answer": answer}, metadata={"result": "accepted", "latency_ms": latency_ms}, ) langfuse.flush() print(f"trace sent, latency_ms={latency_ms}") PY运行:
python demo_trace.py看到类似输出:
trace sent, latency_ms=801回到 Langfuse 面板,进入项目的 Traces 页面,应该能看到local-ai-acceptance-demo。
点进去后,能看到:
- trace 名称
- user_id / session_id
- input / output
- generation 名称
- prompt 内容
- mock 模型名
- token usage
- latency metadata
- tags
到这里,本地 trace 链路已经通了。
再加一条失败 trace,验收时更有用
只看成功请求没什么价值。团队验收时,失败请求反而更能说明观测是否完整。
再写一个失败脚本demo_error_trace.py:
cat > demo_error_trace.py <<'PY' import os from langfuse import Langfuse langfuse = Langfuse( public_key=os.environ["LANGFUSE_PUBLIC_KEY"], secret_key=os.environ["LANGFUSE_SECRET_KEY"], host=os.environ.get("LANGFUSE_HOST", "http://localhost:3000"), ) trace = langfuse.trace( name="local-ai-acceptance-error-demo", user_id="test-user-002", session_id="acceptance-session-20260714", input={"question": "请查询订单:空字符串"}, metadata={"app": "customer-service-demo", "case": "empty_order_id"}, tags=["local", "acceptance", "error-case"], ) trace.event( name="validate-input", input={"order_id": ""}, output={"ok": False, "reason": "order_id is empty"}, level="ERROR", status_message="订单号为空,业务校验未通过", ) trace.update( output={"error": "order_id is empty"}, metadata={"result": "rejected"}, ) langfuse.flush() print("error trace sent") PY运行:
python demo_error_trace.py回到 Langfuse,把 tags 过滤到error-case,就能看到失败样例。验收时让测试同事重点看这一条:输入是否脱敏、错误原因是否明确、错误等级是否正确、业务字段是否足够定位问题。
接真实 OpenAI 兼容接口时怎么写
上面的 mock 示例用来验证链路。接真实模型时,思路不变:在发起 LLM 请求前后,把 prompt、输出、usage、耗时写进 generation。
下面是一个 OpenAI 兼容接口的写法。你可以接 OpenAI、通义兼容接口、硅基流动,或者公司内部网关。
pip install -U openai langfuseimport os import time from openai import OpenAI from langfuse import Langfuse client = OpenAI( api_key=os.environ["OPENAI_API_KEY"], base_url=os.environ.get("OPENAI_BASE_URL"), ) langfuse = Langfuse( public_key=os.environ["LANGFUSE_PUBLIC_KEY"], secret_key=os.environ["LANGFUSE_SECRET_KEY"], host=os.environ.get("LANGFUSE_HOST", "http://localhost:3000"), ) trace = langfuse.trace( name="real-llm-call-demo", user_id="test-user-003", input={"question": "用一句话解释 Langfuse trace 的价值"}, tags=["real-llm", "acceptance"], ) messages = [ {"role": "system", "content": "你是一个技术文档助手,回答要短。"}, {"role": "user", "content": "用一句话解释 Langfuse trace 的价值"}, ] start = time.time() resp = client.chat.completions.create( model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"), messages=messages, temperature=0.2, ) latency_ms = int((time.time() - start) * 1000) answer = resp.choices[0].message.content usage = resp.usage trace.generation( name="chat-completion", model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"), input=messages, output=answer, usage={ "input": usage.prompt_tokens if usage else 0, "output": usage.completion_tokens if usage else 0, "total": usage.total_tokens if usage else 0, }, metadata={"latency_ms": latency_ms}, ) trace.update(output={"answer": answer}) langfuse.flush() print(answer)这里有个小细节:不要把真实用户手机号、身份证、合同内容、内部系统 token 原样写进 trace。验收样例用测试数据,线上数据做脱敏字段。
Langfuse 做 LLM 可观测很顺手,但观测系统本身也会沉淀敏感信息。这个边界必须提前说清楚。
观测面板验收看什么
Trace 能上报以后,就可以准备团队验收了。别把链接一丢就结束,最好提前准备一个检查清单。
我一般让团队看这些项:
| 验收项 | 在 Langfuse 里怎么看 |
|---|---|
| 请求是否进入系统 | Traces 列表能看到目标 trace |
| 输入是否脱敏 | 点开 trace,看 input 和 generation input |
| 输出是否符合预期 | 看 trace output 和 generation output |
| Prompt 是否是当前版本 | 看 generation input 或 metadata 里的 git_branch |
| token 是否有记录 | 看 generation usage |
| 耗时是否可解释 | 看时间线和 latency metadata |
| 失败是否能定位 | 看 level、status_message、error-case tag |
| 是否能按用户或会话追踪 | 看 user_id、session_id |
| 是否能按版本筛选 | 看 tags、metadata、branch 信息 |
验收时可以准备三类 trace:
- 成功请求:证明主链路通
- 业务失败:比如参数为空、知识库无命中、权限不足
- 模型失败:比如模型接口超时、JSON 输出不合法、内容被拦截
这样产品、测试、后端看到的是同一套证据,而不是各看各的日志。
用 cpolar 临时开放 Langfuse HTTPS 面板
本地面板只能你自己打开。团队远程验收时,用 cpolar 把localhost:3000临时映射成 HTTPS。
安装 cpolar 后,先登录并配置 authtoken。token 在 cpolar 控制台里复制:
cpolar authtoken 替换成你的_cpolar_authtoken启动临时隧道:
cpolar http 3000终端会输出一个公网地址,类似:
Forwarding https://xxxx.cpolar.top -> http://localhost:3000把这个 HTTPS 地址发给团队成员。对方打开后,看到的就是你本机的 Langfuse Web 面板。
这里有几个操作我会固定做:
- 给团队单独创建验收账号,不共用你的管理员账号
- 验收前确认 trace 数据是脱敏样例
- 只开放
3000Web 面板端口 - 不开放 Postgres、ClickHouse、Redis、MinIO、模型服务、公司内网 API
- 验收窗口结束后立刻
Ctrl+C停掉 cpolar
如果你使用的是固定域名隧道,也要给它明确的使用窗口。别因为“链接还能打开”就一直挂着。
安全边界:这几条别偷懒
Langfuse 面板里会沉淀 prompt、用户输入、模型输出、token 用量、错误信息、内部接口路径。它不是一个普通静态页面。
所以 cpolar 在这里的角色是“短时验收入口”,不是长期公网发布方案。
我会按下面这几条做:
- 只使用测试项目,不把生产 trace 混进验收项目
- prompt、用户输入、模型输出提前脱敏
- API Key、Cookie、Authorization Header 不写入 trace
- 创建单独验收账号,验收后禁用或删除
- 只开放 Langfuse Web 端口
3000 - 数据库、ClickHouse、Redis、MinIO、模型网关全部留在内网
- cpolar 终端窗口保持可见,验收结束马上关闭
- 验收结束后清理测试 trace 或删除测试项目
还有一点很容易忽略:截图也算数据外发。
如果团队要把 Langfuse 页面截图贴到群里,先确认里面没有真实用户问题、内部 prompt、供应商 Key、订单号、手机号、邮箱、合同文本。很多泄露不是发生在公网入口,而是发生在“随手截个图”。
常见问题和排错
1.docker compose up -d后面板打不开
先看容器状态:
cd ~/ai-observe-demo/langfuse docker compose ps再看 Web 日志:
docker compose logs --tail=100 langfuse-web如果 Web 容器不断重启,通常是 secret、数据库连接、端口占用、依赖容器没起来。先确认3000没被占用:
lsof -i :3000被占用就停掉旧服务,或者调整 Compose 里的端口映射。
2. Python 脚本运行成功,但 Langfuse 看不到 trace
按这个顺序查:
echo $LANGFUSE_HOST echo $LANGFUSE_PUBLIC_KEY echo $LANGFUSE_SECRET_KEY确认LANGFUSE_HOST是:
http://localhost:3000然后确认脚本最后执行了:
langfuse.flush()Langfuse SDK 会异步发送数据,不 flush 的话,短脚本结束太快,trace 还没来得及发出去。
还要检查 API Key 属于当前项目。很多人会在 Langfuse 里建两个项目,然后拿 A 项目的 Key 去 B 项目里找 trace,当然找不到。
3. token usage 为空
mock 示例里我手动写了 usage。真实模型接口里,要看供应商是否返回 usage 字段。
OpenAI 兼容接口一般会返回:
resp.usage.prompt_tokens resp.usage.completion_tokens resp.usage.total_tokens如果你的网关没返回 usage,可以先在 metadata 里记录估算值,或者让网关层补齐 token 统计。验收时要把“真实统计”和“估算统计”区分清楚。
4. trace 里时间不对
本地容器、浏览器、服务器时区不一致时,列表时间看起来会很怪。
先看宿主机时间:
date再看容器时间:
docker exec -it langfuse-langfuse-web-1 date容器名以docker compose ps输出为准。验收结论里统一写北京时间或 UTC,别让大家用各自电脑显示的时间互相对。
5. cpolar 地址能打开,但登录后页面异常
先确认本地http://localhost:3000是否正常。如果本地都异常,先修 Langfuse。
如果本地正常,cpolar 页面异常,检查隧道是不是映射到了正确端口:
cpolar http 3000不要映射到数据库端口,也不要映射到 ClickHouse、Redis、MinIO。团队只需要看 Web 面板。
6. 团队成员能看到不该看的 trace
这是权限和数据隔离问题,不是 cpolar 的问题。
处理方式很直接:新建一个专门验收项目,只导入或生成脱敏 trace;不要把生产项目拿来共享。验收账号只给这个项目权限,验收完禁用账号、删除测试项目或清理 trace。
7. 端口 3000 被别的服务占了
查占用:
lsof -i :3000停掉占用进程,或者修改 Compose 端口映射。比如把宿主机端口换成3100:
ports: - "3100:3000"然后本地访问:
http://localhost:3100cpolar 也跟着换:
cpolar http 3100关闭公网入口和清理现场
验收结束后,我会按这个顺序收尾。
停掉 cpolar:
在运行 cpolar 的终端按 Ctrl+C确认外部 HTTPS 地址已经打不开。
如果只是结束远程验收,Langfuse 可以继续留在本机。但如果这次只是临时演示,也可以停掉整套容器:
cd ~/ai-observe-demo/langfuse docker compose down如果要连测试数据一起清理,执行前先确认没有要保留的 trace:
docker compose down -v-v会删除 Compose 创建的数据卷,Postgres、ClickHouse、MinIO 里的本地数据都会被清掉。这个命令用于临时演示环境,不要在需要保留数据的机器上随手敲。
最后,把验收账号禁用或删除,把临时 API Key 作废,把发到群里的 cpolar 链接标记为已关闭。
写在最后
Langfuse 的价值不只是“看日志”。它真正解决的是 AI 应用验收里的证据问题:一次回答背后的 prompt、输入、输出、耗时、token、错误,能不能被团队一起看见。
cpolar 在这个流程里也不是为了把内网服务永久暴露出去,而是给本地调试台开一个短时、可回收的 HTTPS 验收窗口。
我的经验是,只要把脱敏 trace 准备好,再把安全边界讲清楚,一场 AI Demo 验收会顺很多。大家不用围着截图猜问题,直接点开 trace 看事实。
验收完记得关入口。
这一步看着简单,但它决定了“临时分享”到底是工程流程,还是安全事故的开头。
