schedule6 phút đọc

Quan sát toàn diện Agent trong Cơ sở dữ liệu riêng với LangChain, LangSmith SDK và DeepAgents

Cách theo dõi mỗi lần gọi LLM, gọi công cụ và sub-agent trong LangChain DeepAgents — và lưu trữ tất cả trong MongoDB riêng thay vì LangSmith cloud.

translate
Có sẵn bằng:
infoBài viết này được dịch bằng AI

Mỗi lần gọi agent LangChain tạo ra một cây chạy: agent gốc, sub-agents, tool calls, LLM calls. LangSmith tự động theo dõi điều này. Nhưng nếu bạn cần dữ liệu tracing trong cơ sở dữ liệu riêng thì sao?

Chúng tôi đã xây dựng một hệ thống production theo dõi mỗi lần gọi LLM trong LangChain DeepAgents và lưu trữ trong MongoDB — sử dụng duy nhất các kiểu SDK chính thức, không có subclass BaseTracer tùy chỉnh, không khai báo lại trường. Đây là cách thực hiện.


Vấn đề

LangSmith cloud cung cấp tracing sẵn sàng. Đặt LANGSMITH_TRACING=true, và mỗi model.invoke(), agent.stream() và tool call đều được ghi lại.

Nhưng chúng tôi cần:

  1. Dữ liệu ở lại MongoDB của chúng tôi — prompts, responses, business logic không bao giờ rời khỏi hạ tầng
  2. Business context trên runs — tenant ID, agent type, entity ID gắn với mỗi trace
  3. Không code tracer tùy chỉnh — tái sử dụng quản lý run tree, kế thừa metadata, trích xuất token từ SDK
  4. Hoạt động với DeepAgents — sub-agents, tool calls, toàn bộ cây

Cách tiếp cận ngây thơ là kế thừa BaseTracer và triển khai lại persistRun / _endTrace. Điều này bỏ qua việc truyền dotted_order, kế thừa metadata, trích xuất token và theo dõi time-to-first-token. Đừng làm vậy.


Giải pháp: LangSmithTracingClientInterface

SDK langsmith định nghĩa interface gồm 2 phương thức cho persistence:

interface LangSmithTracingClientInterface {
  createRun(run: RunCreate): Promise<void>;
  updateRun(runId: string, run: RunUpdate): Promise<void>;
}

LangChainTracer từ @langchain/core xử lý mọi thứ — quản lý run tree, truyền dotted_order, trace_id, kế thừa metadata/tags, trích xuất token từ đầu ra LLM — và ủy quyền storage cho interface này.

Bằng cách triển khai nó cho MongoDB, bạn có được toàn bộ SDK miễn phí.

graph TD
  A["Code agent của bạn"] -->|"callbacks: [tracer]"| B["LangChainTracer"]
  B -->|"quản lý run tree"| C["RunTree"]
  C -->|"các DTO phẳng"| D["MongoDBLangSmithClient"]
  D -->|"createRun(RunCreate)"| E["MongoDB"]
  D -->|"updateRun(RunUpdate)"| E
  B -->|"kế thừa metadata"| F["Tất cả child runs nhận traceId, tags"]
  D -->|"trích xuất token"| G["usage_metadata trên các LLM run"]

Bước 1: MongoDB Client

Triển khai LangSmithTracingClientInterface. Phương thức createRun nhận một DTO RunCreate phẳng — không có tham chiếu vòng lặp, không có cây child_runs, không cần sanitizeForBson. Chỉ cần insert nó.

import type { LangSmithTracingClientInterface } from 'langsmith';
import type { RunCreate, RunUpdate } from 'langsmith/schemas';

class MongoDBLangSmithClient implements LangSmithTracingClientInterface {
  constructor(
    private readonly storage: ObservabilityStorage,
    private readonly businessContext?: { tenantId: ObjectId; entity: ObjectId },
  ) {}

  async createRun(run: RunCreate): Promise<void> {
    const { child_runs, ...doc } = run;
    await this.storage.insertLangChainRun({
      ...doc,
      businessContext: this.businessContext,
    });
  }

  async updateRun(runId: string, run: RunUpdate): Promise<void> {
    await this.storage.updateLangChainRun(runId, run);
  }
}

businessContext là metadata tùy chọn bạn gắn vào mỗi run — tenant, agent type, bất cứ thứ gì ứng dụng cần. Nó không thuộc model LangChain; nó là của bạn.


Bước 2: Hàm Factory

Tạo một factory trả về LangChainTracer tiêu chuẩn được hỗ trợ bởi MongoDB client:

import { LangChainTracer } from '@langchain/core/tracers/tracer_langchain';

function createMongoDBTracer(
  traceId: string,
  storage: ObservabilityStorage,
  businessContext?: { tenantId: ObjectId; entity: ObjectId },
  metadata?: Record<string, unknown>,
): LangChainTracer {
  const client = new MongoDBLangSmithClient(storage, businessContext);
  return new LangChainTracer({
    client,
    projectName: 'default',
    metadata: { traceId, ...metadata },
  });
}

Sử dụng nó ở bất cứ đâu bạn gọi LangChain runnable:

const traceId = uuid7(); // UUID có thứ tự thời gian từ SDK langsmith
const tracer = createMongoDBTracer(traceId, storage, {
  tenantId, entityId, agentType: 'sales-assistant',
});

await agent.stream(
  { messages: [{ role: 'user', content: message }] },
  { callbacks: [tracer] },
);

Chỉ vậy thôi. Mỗi run trong cây — agent gốc, sub-agents, LLM calls, tool calls — đều được theo dõi với dotted_order, trace_id đúng, kế thừa metadata và đếm token chính xác.


Bước 3: Schema Lưu trữ

Mở rộng RunCreate từ langsmith/schemas trực tiếp. Đừng khai báo lại các trường:

import type { RunCreate } from 'langsmith/schemas';

interface LangChainRunDoc extends RunCreate {
  _id: ObjectId;
  businessContext?: {
    tenantId: ObjectId;
    entityId: ObjectId;
    agentType?: string;
  };
  createdAt: Date;
}

RunCreate đã định nghĩa id, name, start_time, run_type, inputs, outputs, extra, tags, events, trace_id, dotted_order, parent_run_id. Bạn chỉ thêm các trường business của riêng mình.


Bước 4: Những gì bạn nhận được tự động

Quản lý Run Tree

LangChainTracer sử dụng RunTree bên trong. Khi run bắt đầu, nó gọi createRun với một DTO phẳng. Khi kết thúc, nó gọi updateRun với outputs, error, end_time và metadata bổ sung. Bạn không quản lý cây — SDK xử lý.

Kế thừa Metadata

Truyền metadata cho tracer, và nó sẽ truyền đến tất cả child runs:

const tracer = createMongoDBTracer(traceId, storage, ctx, {
  thread_id: conversationId,  // cho các truy vấn theo thread
  agentType: 'pricing',
});

Mỗi child run (sub-agents, tools, LLM calls) nhận thread_id trong extra.metadata. Truy vấn theo thread trong MongoDB:

db.langchain_runs.find({ 'extra.metadata.thread_id': conversationId })

Trích xuất Token

LangChainTracer.onLLMEnd trích xuất usage_metadata từ đầu ra chat model và lưu trữ trong run.extra.metadata.usage_metadata. Không cần trích xuất tùy chỉnh.

Time-to-First-Token

Đối với các LLM run có streaming, LangChainTracer theo dõi first_token_time qua RunTree. Lệnh updateRun của bạn nhận được điều này trong RunUpdate.


Bước 5: Truy vấn

Run gốc (nơi parent_run_id không tồn tại) CHÍNH LÀ phiên trace. Truy vấn trực tiếp:

// Lấy tất cả runs cho một trace
async findRunsByTraceId(traceId: string) {
  return db.langchain_runs.find({ traceId }).sort({ dotted_order: 1 });
}

// Lấy root run (phiên)
async findRootRun(traceId: string) {
  return db.langchain_runs.findOne({
    traceId,
    parent_run_id: { $exists: false },
  });
}

// Lấy runs theo message ID (từ extra.metadata)
async findRunsByMessageId(messageId: string) {
  return db.langchain_runs.find({
    'extra.metadata.message_id': messageId,
  });
}

Không cần joins, không cần collection phiên riêng biệt. Một collection, một truy vấn.


Bước 6: Dữ liệu trông như thế nào

Mỗi document trong langchain_runs trông như thế này:

{
  "_id": "...",
  "id": "run-uuid-7",
  "name": "ChatOpenAI",
  "run_type": "llm",
  "trace_id": "root-run-uuid",
  "parent_run_id": "parent-uuid",
  "dotted_order": "20260722T150322...1234.20260722T150322...5678",
  "start_time": 1753216202000,
  "end_time": 1753216203500,
  "inputs": { "messages": [...] },
  "outputs": { "generations": [...] },
  "extra": {
    "metadata": {
      "ls_provider": "openai",
      "ls_model_name": "gpt-4o",
      "thread_id": "conversation-123",
      "usage_metadata": {
        "input_tokens": 1250,
        "output_tokens": 340,
        "total_tokens": 1590
      }
    }
  },
  "tags": ["sales-assistant", "pricing"],
  "businessContext": {
    "tenantId": "...",
    "entityId": "...",
    "agentType": "pricing"
  },
  "createdAt": "2026-07-22T15:03:22.000Z"
}

Cấu hình LLM (provider, model) đến từ extra.metadata.ls_providerextra.metadata.ls_model_name — không cần snapshot.


Tích hợp DeepAgents

DeepAgents xây dựng trên LangGraph, sử dụng LangChain runnables. Cùng mẫu callbacks: [tracer] theo dõi toàn bộ cây deep agent:

graph TD
  A["DeepAgent"] -->|"callbacks"| B["LangChainTracer"]
  B --> C["Root run: agent"]
  C --> D["Sub-agent: pricing"]
  C --> E["Sub-agent: scheduling"]
  D --> F["LLM call: ChatOpenAI"]
  D --> G["Tool: calculatePrice"]
  E --> H["LLM call: ChatOpenAI"]
  E --> I["Tool: checkAvailability"]
  F -->|"parent_run_id = D"| J["langchain_runs"]
  G -->|"parent_run_id = D"| J
  H -->|"parent_run_id = E"| J
  I -->|"parent_run_id = E"| J
  C -->|"parent_run_id không tồn tại"| J
  J -->|"dotted_order sắp xếp cây"| K["Hiển thị waterfall"]

Mỗi run có parent_run_id liên kết với cha của nó, và dotted_order để sắp xếp theo thứ tự phân cấp. Toàn bộ cây có thể được tái tạo từ một truy vấn traceId.


Chỉ mục MongoDB

db.langchain_runs.createIndex({ runId: 1 }, { unique: true });
db.langchain_runs.createIndex({ traceId: 1, dotted_order: 1 });
db.langchain_runs.createIndex({ parent_run_id: 1 });
db.langchain_runs.createIndex({ 'extra.metadata.message_id': 1 }, { sparse: true });

Chỉ mục traceId + dotted_order bao quát truy vấn phổ biến nhất: lấy tất cả runs cho một trace theo thứ tự thực thi.


Những gì chúng tôi đã xóa

Sau khi chuyển sang cách tiếp cận này, chúng tôi đã loại bỏ:

Thứ gì Tại sao
Subclass BaseTracer tùy chỉnh LangChainTracer xử lý mọi thứ
Hàm sanitizeForBson DTO phẳng không có tham chiếu vòng lặp
Truyền dotted_order / trace_id thủ công SDK xử lý
Collection trace_sessions riêng biệt Root run CHÍNH LÀ phiên
Trường snapshot cấu hình LLM Bắt nguồn từ extra.metadata khi truy vấn
13 phương thức CRUD phiên Được thay thế bằng các truy vấn root-run

Những lỗi thường gặp

Đừng kế thừa BaseTracer trực tiếp. Nó là abstract và bỏ qua quản lý run tree. Sử dụng LangChainTracer với client tùy chỉnh.

Đừng lưu trữ các đối tượng Run (có child_runs). Chúng có tham chiếu vòng lặp. LangChainTracer đã chuyển đổi thành các DTO phẳng RunCreate / RunUpdate.

Đừng khai báo lại các trường BaseRun trong kiểu tùy chỉnh. Mở rộng RunCreate từ langsmith/schemas trực tiếp.

Đừng snapshot cấu hình LLM mỗi lần chạy. Provider và model nằm trong extra.metadata.ls_providerextra.metadata.ls_model_name. Bắt nguồn khi truy vấn.

Đừng quên thread_id trên tất cả runs. Truyền nó trong RunnableConfig.metadata. LangChainTracer truyền nó đến child runs. Nếu không, các truy vấn theo thread sẽ thất bại.


Đọc thêm


Câu hỏi thường gặp

Tại sao không sử dụng LangSmith cloud cho việc tracing? Chủ quyền dữ liệu. Prompts LLM, responses và business logic của bạn rời khỏi cơ sở hạ tầng. Đối với các ngành được quản lý hoặc nhạy cảm cạnh tranh, việc lưu trữ dữ liệu tracing trong cơ sở dữ liệu riêng là bắt buộc. Bạn cũng tránh được giá theo từng trace khi mở rộng.

LangSmithTracingClientInterface là gì? Một interface gồm 2 phương thức từ SDK langsmith: createRunupdateRun. LangChainTracer ủy quyền toàn bộ persistence cho interface này. Bằng cách triển khai nó cho MongoDB, bạn có được quản lý run tree đầy đủ của SDK, kế thừa metadata và trích xuất token — trong khi dữ liệu được lưu trữ cục bộ.

Tại sao mở rộng RunCreate thay vì tạo kiểu tùy chỉnh? RunCreate từ langsmith/schemas là DTO persistence chính thức của LangChain. Đây là những gì LangChainTracer gửi đến client. Bằng cách mở rộng trực tiếp, bạn loại bỏ việc khai báo lại trường, tránh sai lệch khi SDK cập nhật và có được persistence an toàn kiểu miễn phí.

Nó có hoạt động với sub-agents của DeepAgents không? Có. DeepAgents xây dựng trên LangGraph, sử dụng LangChain runnables. Cùng LangChainTracer với callbacks theo dõi toàn bộ cây: agent gốc, sub-agents, tool calls, LLM calls. Mỗi lần chạy của sub-agent được liên kết qua parent_run_iddotted_order.

Làm thế nào để nhóm theo thread mà không cần LangSmith? truyền thread_id trong metadata RunnableConfig. LangChainTracer truyền nó đến tất cả child runs. Truy vấn theo thread_id trong collection MongoDB. Điều này giống hệt cách LangSmith threads hoạt động — bạn chỉ sở hữu storage.


Tags: langchain langsmith deep-agents observability mongodb llm-tracing ai-agents typescript langsmith-tracing-client runcreate langchain-tracer

Bài viết hữu ích? Hãy chia sẻ.

Bài viết liên quan

Cảm ơn bạn đã đọc!
Đọc thêm bài viết