Skip to content

17. LangSmith Studio:给你的 AI Agent 装一个"透视眼镜" ​

简单来说 ​

LangSmith Studio 是一个免费的可视化调试界面,它能让你实时看到 Agent 内部的每一步操作:发了什么 Prompt、调用了哪些工具、工具返回了什么、消耗了多少 Token。从此告别 console.log 地狱,让本地开发 Agent 的体验从"蒙着眼开车"变成"开着导航跑高速"。


🎯 本节目标 ​

学完本节,你将能够回答:

  1. LangSmith Studio 能解决什么调试痛点?
  2. 如何配置本地 Agent 连接到 Studio?
  3. 如何使用 Trace 追踪功能定位问题?
  4. 什么是热重载(Hot-reload)?它如何提升开发效率?
  5. 如何从对话的任意节点重新运行测试?

核心痛点与解决方案 ​

痛点:开发 AI Agent 就像在黑箱里摸鱼 ​

在没有 Studio 之前,开发 AI Agent 会遇到这些倒霉事:

😵 痛点具体表现
黑箱调试Agent 给了个奇怪的回答,你完全不知道它中间经历了什么
反复重启改一行代码就要重新运行整个对话流程,从头测试
日志地狱为了调试,疯狂 console.log,最后代码里全是打印语句
状态难追某个 bug 只在特定对话上下文中出现,复现困难
Token 花费盲区不知道每次调用消耗了多少 token,成本控制无从谈起

解决:Studio 让一切"可视化 + 可交互" ​

Studio 的解决思路很直接:把 Agent 的"大脑活动"全部可视化,并允许你像玩游戏存档一样,从任意节点重新开始。

开发 Agent 的传统方式:

  你 → 运行代码 → Agent 输出 → 🤷 "中间发生了什么?"
        │
        └── 出 bug → console.log → 重启 → 再测试 → 崩溃...

使用 Studio 的方式:

  你 → 运行代码 → Studio 可视化追踪
                      │
                      ├── Prompt 发了什么? ✅ 清晰可见
                      ├── 工具调用了什么? ✅ 参数、返回值全有
                      ├── 哪一步出问题? ✅ 一眼定位
                      └── 从那一步重跑? ✅ 一键搞定

Agent 开发痛点与 Studio 解决方案对比


生活化类比 ​

把 Agent 开发比作"拍电影" 🎬 ​

想象你是一个电影导演,正在拍一部 AI 主演的电影:

电影概念LangGraph 对应说明
演员AgentAI 主演,执行各种任务
道具Tool (工具)发邮件工具 = 一部道具手机
剧本台词Prompt导演给演员的指示
导演监视器LangSmith Studio看每一个镜头的执行细节
拍摄录像Trace (追踪)完整记录每个镜头
一场戏的录像Thread (线程)从开拍到喊卡的完整记录

没有 Studio 的情况:

  • 导演喊"开始!",演员演完了,导演只能看到最终结果
  • 演员中间演砸了,导演不知道是哪个环节出问题
  • 想重拍中间某一段?对不起,必须从头来

有了 Studio 的情况:

  • 导演面前有个大监视器,能看到演员的每一个表情、每一句台词
  • 演砸了?导演可以回放录像,定位到具体哪一秒出的问题
  • 想重拍中间某一段?直接从那个镜头开始,不用从头化妆

电影拍摄类比:Studio 核心概念映射


核心组件详解 ​

1. 环境准备 ​

使用 Studio 需要准备以下内容:

准备项说明获取方式
LangSmith 账号免费注册smith.langchain.com
LangSmith API Key连接凭证在 LangSmith 设置页面创建
LangGraph CLI本地开发服务器npx @langchain/langgraph-cli

2. 安装 LangGraph CLI ​

bash
npx @langchain/langgraph-cli

💡 人话解读: CLI 就像一根 USB 数据线,把你的 Agent(手机)连接到 Studio(电脑)上。

3. 创建示例 Agent ​

typescript
import { createAgent } from "@langchain/langgraph";

function sendEmail(to: string, subject: string, body: string): string {
  const email = { to, subject, body };
  console.log("Sending email:", email);
  return `Email sent to ${to}`;
}

const agent = createAgent({
  model: "gpt-4.1",
  tools: [sendEmail],
  systemPrompt: "You are an email assistant. Always use the sendEmail tool.",
});

export { agent };

💡 人话解读:

  • sendEmail 函数 —— 给 Agent 配备的"技能",就像给员工一部能发邮件的手机
  • createAgent —— 把大模型、工具、提示词组装成一个"智能员工"
  • export { agent } —— 让外部(CLI)能找到并运行这个 Agent

4. 配置 langgraph.json ​

json
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent.ts:agent"
  },
  "env": ".env"
}

逐行人话翻译:

配置项意思
"dependencies": ["."]"去当前目录找依赖"
"graphs": {...}"我的 Agent 在这些位置"
"agent": "./src/agent.ts:agent""Agent 在 agent.ts 文件里,导出名叫 agent"
"env": ".env""环境变量在 .env 文件里"

5. 配置环境变量 ​

创建 .env 文件:

bash
LANGSMITH_API_KEY=lsv2_xxx...
OPENAI_API_KEY=sk-xxx...

⚠️ 重要提醒:

  • 确保 .env 文件不要提交到 Git!
  • 在 .gitignore 中添加 .env

6. 项目结构 ​

plaintext
my-app/
├── src/
│   └── agent.ts           # Agent 代码
├── .env                   # 环境变量(不要提交到 Git!)
├── package.json           # 依赖清单
└── langgraph.json         # LangGraph 配置文件

7. 启动 Studio ​

bash
npx @langchain/langgraph-cli dev

启动后会发生什么:

  • 本地 Agent Server 启动在 http://127.0.0.1:2024
  • 自动打开 Studio UI:https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
  • 热重载模式开启,代码修改自动生效

Studio 环境配置 6 步流程


Studio 核心功能 ​

1. 执行追踪 (Trace) ​

Trace 是 Agent 执行过程的"完整录像",包含:

Trace 示例:
├── Step 1: 收到用户输入
│   └── 内容: "帮我给 Alice 发一封会议邀请邮件"
│
├── Step 2: LLM 推理
│   ├── Prompt: [system + user 消息]
│   ├── Token 消耗: 150 tokens
│   └── 延迟: 1.2s
│
├── Step 3: 工具调用
│   ├── 工具: sendEmail
│   ├── 参数: { to: "alice@example.com", subject: "会议邀请", body: "..." }
│   └── 返回: "Email sent to alice@example.com"
│
└── Step 4: 最终输出
    └── 内容: "已成功发送邮件给 Alice!"

💡 人话解读: 就像行车记录仪,记录了 Agent 从起点到终点的每一个动作,出了问题随时回放查看。

2. 热重载 (Hot-reload) ​

传统开发流程:
  修改代码 → 停止服务 → 重新启动 → 测试 → 发现问题 → 再改... 🔄

Studio 热重载:
  修改代码 → 自动更新 → 直接测试 ✅

改代码后,Studio 自动感知并更新,不用手动重启服务器! 这个功能省去了无数次"改代码 → 停服务 → 重启服务 → 测试"的循环。

3. Thread 回放 ​

Thread 是一次完整的对话会话,你可以:

  • 从任意节点重新运行 —— 不用从头开始
  • 修改中间状态 —— 测试不同的输入
  • 对比不同版本 —— 看修改前后的效果差异
对话 Thread:
├── [用户] 帮我查询订单 #12345
├── [Agent] 调用 queryOrder 工具
├── [工具返回] { status: "已发货" }    ← 可以从这里重跑!
└── [Agent] 您的订单已发货...

4. Token/延迟指标 ​

Studio 清晰展示每次调用的资源消耗:

指标说明
Token 消耗每次 LLM 调用用了多少 token
延迟每个步骤花了多长时间
成本估算大概花了多少钱(基于 token 价格)

Studio 四大核心功能全景


业务场景:电商客服 Agent 调试实战 ​

场景描述 ​

你正在开发一个电商客服 AI,它需要:

  • 查询订单状态
  • 处理退款请求
  • 回答商品问题

问题出现 ​

用户:我的订单 #12345 怎么还没发货?

Agent:[返回了一个奇怪的回答] "抱歉,我无法找到相关信息..."

你:???它中间到底干了啥?

没有 Studio 的痛苦调试 ​

typescript
async function queryOrder(orderId: string) {
  console.log("queryOrder called with:", orderId);  // 加日志
  const result = await database.query(orderId);
  console.log("queryOrder result:", result);        // 加日志
  return result;
}

// 3 小时后...
// 终于发现是订单号格式解析错误
// 用户说的是 "#12345",但工具收到的是 "12345"

有了 Studio 的丝滑调试 ​

步骤 1:打开 Studio,查看 Trace

Trace 显示:
├── Step 1: 收到用户输入
│   └── 内容: "我的订单 #12345 怎么还没发货?"
│
├── Step 2: LLM 推理 → 决定调用 queryOrder
│
├── Step 3: 工具调用 queryOrder
│   ├── 参数: { orderId: "12345" }  ← 🔴 发现问题!应该是 "#12345"
│   └── 返回: null
│
└── Step 4: LLM 收到空结果,开始瞎编...

步骤 2:定位问题

原来是订单号格式解析问题!# 符号被丢失了。

步骤 3:修复代码

typescript
function parseOrderId(input: string): string {
  const match = input.match(/#?(\d+)/);
  return match ? `#${match[1]}` : input;
}

async function queryOrder(orderId: string) {
  const normalizedId = parseOrderId(orderId);
  return await database.query(normalizedId);
}

步骤 4:热重载自动更新,从 Step 3 重新运行

Trace 显示:
├── Step 3: 工具调用 queryOrder(重跑)
│   ├── 参数: { orderId: "#12345" }  ← ✅ 格式正确
│   └── 返回: { status: "已发货", trackingNumber: "SF123..." }
│
└── Step 4: 正确回复
    └── "您的订单 #12345 已发货,快递单号 SF123..."

效率对比 ​

方面没有 Studio有 Studio
定位问题3 小时5 分钟
代码侵入满屏 console.log零侵入
测试效率每次从头跑从断点重跑
Token 消耗不清楚一目了然
心情指数😤 暴躁😊 平和

调试体验对比:无 Studio vs 有 Studio


完整示例:带调试支持的客服 Agent ​

项目结构 ​

plaintext
customer-service-bot/
├── src/
│   ├── tools/
│   │   └── orderTools.ts    # 订单查询工具
│   ├── agent.ts             # Agent 定义
│   └── index.ts             # 入口文件
├── .env
├── package.json
└── langgraph.json

客服 Agent 项目架构与执行流程

工具定义 (src/tools/orderTools.ts) ​

typescript
import { tool } from "@langchain/core/tools";
import { z } from "zod";

export const queryOrderTool = tool(
  async ({ orderId }: { orderId: string }) => {
    const normalizedId = orderId.replace(/^#/, "");
    
    const mockDatabase: Record<string, any> = {
      "12345": { status: "已发货", tracking: "SF1234567890" },
      "12346": { status: "处理中", tracking: null },
      "12347": { status: "已完成", tracking: "YT9876543210" },
    };
    
    const order = mockDatabase[normalizedId];
    if (!order) {
      return JSON.stringify({ error: "订单不存在" });
    }
    
    return JSON.stringify({
      orderId: `#${normalizedId}`,
      ...order,
    });
  },
  {
    name: "queryOrder",
    description: "查询订单状态,输入订单号(可以带或不带 # 前缀)",
    schema: z.object({
      orderId: z.string().describe("订单号,如 #12345 或 12345"),
    }),
  }
);

export const requestRefundTool = tool(
  async ({ orderId, reason }: { orderId: string; reason: string }) => {
    return JSON.stringify({
      success: true,
      message: `退款申请已提交,订单 ${orderId},原因:${reason}`,
      estimatedTime: "3-5 个工作日",
    });
  },
  {
    name: "requestRefund",
    description: "申请退款",
    schema: z.object({
      orderId: z.string().describe("订单号"),
      reason: z.string().describe("退款原因"),
    }),
  }
);

Agent 定义 (src/agent.ts) ​

typescript
import { StateGraph, START, END, Annotation } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { ToolNode } from "@langchain/langgraph/prebuilt";
import { BaseMessage, AIMessage } from "@langchain/core/messages";
import { queryOrderTool, requestRefundTool } from "./tools/orderTools.js";

const AgentState = Annotation.Root({
  messages: Annotation<BaseMessage[]>({
    reducer: (curr, update) => [...curr, ...update],
    default: () => [],
  }),
});

const tools = [queryOrderTool, requestRefundTool];
const toolNode = new ToolNode(tools);

const llm = new ChatOpenAI({
  model: "gpt-4o-mini",
}).bindTools(tools);

async function callModel(state: typeof AgentState.State) {
  const response = await llm.invoke([
    {
      role: "system",
      content: `你是一个友好的电商客服助手。
      你可以帮用户:
      1. 查询订单状态 - 使用 queryOrder 工具
      2. 申请退款 - 使用 requestRefund 工具
      
      请始终保持礼貌和专业。`,
    },
    ...state.messages,
  ]);
  
  return { messages: [response] };
}

function shouldContinue(state: typeof AgentState.State) {
  const lastMessage = state.messages[state.messages.length - 1];
  
  if (lastMessage instanceof AIMessage && lastMessage.tool_calls?.length) {
    return "tools";
  }
  
  return "end";
}

const graph = new StateGraph(AgentState)
  .addNode("agent", callModel)
  .addNode("tools", toolNode)
  .addEdge(START, "agent")
  .addConditionalEdges("agent", shouldContinue, {
    tools: "tools",
    end: END,
  })
  .addEdge("tools", "agent");

export const customerServiceAgent = graph.compile();

langgraph.json ​

json
{
  "dependencies": ["."],
  "graphs": {
    "customer_service": "./src/agent.js:customerServiceAgent"
  },
  "env": ".env"
}

.env ​

bash
LANGSMITH_API_KEY=lsv2_xxx...
OPENAI_API_KEY=sk-xxx...

启动调试 ​

bash
npm run build

npx @langchain/langgraph-cli dev

避坑指南 ​

❌ 坑 1:Safari 浏览器无法连接 ​

Safari 会阻止 localhost 连接。

解决方案:

bash
npx @langchain/langgraph-cli dev --tunnel

然后在 Studio UI 中手动添加 tunnel URL。

❌ 坑 2:.env 文件被提交到 Git ​

解决方案: 在 .gitignore 中添加:

.env
.env.local

❌ 坑 3:忘记导出 Agent ​

配置文件中 "./src/agent.ts:agent" 后面的 agent 是导出名。

错误代码:

typescript
const agent = graph.compile();
// 忘记导出!

正确代码:

typescript
const agent = graph.compile();
export { agent };  // 必须导出!

❌ 坑 4:引用 .ts 文件而非编译后的 .js ​

json
// ❌ 错误
{
  "graphs": {
    "agent": "./src/agent.ts:agent"
  }
}

// ✅ 正确(如果用 TypeScript 编译)
{
  "graphs": {
    "agent": "./dist/agent.js:agent"
  }
}

💡 提示: 如果使用 tsx 或 ts-node 直接运行 TypeScript,可以使用 .ts 文件。

Studio 四大常见坑速查卡片


总结对比表 ​

功能作用类比
Trace执行过程的完整记录行车记录仪
Thread一次完整对话会话一场戏的录像
Hot-reload代码修改自动更新换剧本不用重新化妆
断点重跑从任意节点重新执行从某个镜头重拍
Token 指标资源消耗统计拍摄成本核算

核心要点回顾 ​

  1. Studio 是免费的 —— 注册 LangSmith 账号即可使用
  2. 零代码侵入 —— 不需要在代码里加 console.log
  3. 热重载提升效率 —— 改代码后自动更新,不用重启
  4. 断点重跑 —— 从对话的任意节点重新运行,不用从头开始
  5. Token 可视化 —— 清晰看到每次调用的资源消耗

下一步学习 ​

  • LangSmith 部署:学习如何将 Agent 部署到生产环境
  • 可观测性:监控、日志和性能分析
  • 项目实战:构建完整的多 Agent 协作系统

读文档、看源码、写代码,理解 AI Agent 本质 🤖