---
title: "eve"
description: "每个 eve agent 轮次产生一个 evlog 宽事件：令牌使用量、工具执行和业务上下文，通过你自己的 drain 和尾部采样实现。"
canonical_url: "https://evlog.zhcndoc.com/use-cases/eve"
---
# eve

> 每个 eve agent 轮次产生一个 evlog 宽事件：令牌使用量、工具执行和业务上下文，通过你自己的 drain 和尾部采样实现。

[eve](https://eve.dev/docs/introduction) 内置了可观测性：Vercel 上的 **Agent Runs**，以及通过 `agent/instrumentation.ts` 提供的可选 **OpenTelemetry** spans。`evlog/eve` 增加了第三层：每个轮次都生成**可导出的宽事件**，并接入你完整的 evlog 管道（drains、enrichers、尾部采样、审计）。

<callout color="info" icon="i-lucide-info">

`evlog/eve` 需要 **eve 0.30 或更高版本**。eve 仍处于 beta 阶段，流事件结构可能会在正式发布前发生变化，因此请在生产环境中的 agent 内固定 `eve` 和 `evlog` 的版本。

</callout>

<prompt :actions="["copy","cursor","claude"]" description="为我的 eve agent 添加 evlog 宽事件" icon="i-custom-eve">

为我的 eve agent 添加 evlog 宽事件。

- 安装 evlog：pnpm add evlog
- 在项目根目录使用来自 'evlog' 的 defineEvlog 创建 evlog.config.ts，配置 service、drain、enrich 和 sampling（与 HTTP 中间件集成使用相同的选项）
- 使用来自 'evlog/eve' 的 defineEvlogHook 创建 agent/hooks/evlog.ts，并传入配置：defineEvlogHook({ ...config, /* eve 选项 */ })
- 在工具中从 'evlog/eve' 导入 useLogger，并在 execute() 内调用 useLogger()。注册 defineEvlogHook() 后，轮次 logger 会通过 AsyncLocalStorage 绑定；只有在运行时无法使用 ALS 时才传入 ctx
- 默认省略用户消息内容（message: 'omit'）；只有在检查 PII 策略后才使用 'preview' 或 'full'
- 可选：添加 agent/instrumentation.ts，并使用来自 'evlog/eve' 的 defineEvlogInstrumentation 将 OTel spans 关联到宽事件
- 保留 eve Agent Runs。evlog/eve 是附加功能

文档：[https://www.evlog.dev/use-cases/eve](https://www.evlog.dev/use-cases/eve)
适配器：[https://www.evlog.dev/integrate/adapters/overview](https://www.evlog.dev/integrate/adapters/overview)

</prompt>

## 何时使用什么

<table>
<thead>
  <tr>
    <th>
      需求
    </th>
    
    <th>
      使用
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      在 Vercel 中调试会话
    </td>
    
    <td>
      eve <strong>
        Agent Runs
      </strong>
      
      （自动）
    </td>
  </tr>
  
  <tr>
    <td>
      在 Datadog / Honeycomb 中查看跨度级追踪
    </td>
    
    <td>
      eve <strong>
        <code>
          agent/instrumentation.ts
        </code>
      </strong>
      
       + OTel 导出器
    </td>
  </tr>
  
  <tr>
    <td>
      将宽事件发送到 Axiom / Better Stack / FS，用于计费、审计和尾部采样
    </td>
    
    <td>
      <strong>
        <code>
          evlog/eve
        </code>
      </strong>
    </td>
  </tr>
  
  <tr>
    <td>
      在跨度与其宽事件之间相互跳转
    </td>
    
    <td>
      <strong>
        <code>
          defineEvlogInstrumentation()
        </code>
      </strong>
      
      （<a href="#correlate-traces-with-wide-events">
        见下文
      </a>
      
      ）
    </td>
  </tr>
</tbody>
</table>

## 快速开始

### 1. 安装

<code-group>

```bash [pnpm]
pnpm add evlog eve
```

```bash [bun]
bun add evlog eve
```

```bash [yarn]
yarn add evlog eve
```

```bash [npm]
npm install evlog eve
```

</code-group>

### 2. 添加 hook

将 agent 的设置放在项目根目录的 `evlog.config.ts` 中。它就是 [evlog CLI 读取的文件](https://evlog.zhcndoc.com/cli/config)，其 `extends` 可以引入组织在多个应用之间共享的预设：

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'

export default defineEvlog({
  service: 'my-agent',
  drain: createAxiomDrain(),
  enrich: (ctx) => {
    ctx.event.region = process.env.VERCEL_REGION
  },
})
```

然后创建 `agent/hooks/evlog.ts`，并将配置传给 `defineEvlogHook()`：

```typescript [agent/hooks/evlog.ts]
import { defineEvlogHook } from 'evlog/eve'
import config from '../../evlog.config'

export default defineEvlogHook(config)
```

eve 会自动发现 `agent/hooks/` 下的 hook 文件。不需要 HTTP 中间件，因为工作单元是 agent **轮次**，而不是请求。

第一次调用 hook 时会根据这些设置启动日志器，因此轮次之外发出的事件（例如定时任务的事件）也会使用相同服务到达相同 drain。展开配置即可加入 eve 选项，展开之后的内容会覆盖文件中的设置：

```typescript [agent/hooks/evlog.ts]
export default defineEvlogHook({
  ...config,
  message: 'preview',
  sessionEvent: true,
})
```

没有配置文件时，可以直接将相同设置传给 `defineEvlogHook()`。服务名称默认为 `eve-agent`。

`enrich` 的调用形态与 HTTP 集成一致：它只能看到事件，无法访问 eve 会话。若要将会话上下文写入轮次事件，例如记录 `eve.caller` 未包含的调用方信息，请使用 `enrichTurn`。创建轮次 logger 时会调用一次该函数：

```typescript [agent/hooks/evlog.ts]
export default defineEvlogHook({
  enrichTurn: (ctx) => ({
    tier: ctx.session.auth.current?.principalId === 'github:1' ? 'pro' : 'free',
  }),
})
```

返回字段会合并到轮次事件中，并覆盖内置的 `eve`、`agent` 和 `channel` 字段；它们仅作用于当前轮次，不会传递到该会话的下一轮。若 `enrichTurn` 抛出异常，evlog 会记录异常并跳过该 enrich，不会使轮次失败。

### 3. 从工具中记录业务上下文

`defineEvlogHook()` 会在 `turn.started` 上通过 **AsyncLocalStorage** 绑定轮次 logger。在工具的 `execute()` 处理器内，不带参数调用 `useLogger()`，其使用方式与 Nuxt 或 Hono 中的 `useLogger(event)` 相同。

在[示例 agent](https://github.com/evloghq/evlog/tree/main/examples/eve) 中，支持工具会在 agent 处理退款时附加客户和订单上下文：

```typescript [agent/tools/lookup_order.ts]
import { defineTool } from 'eve/tools'
import { useLogger } from 'evlog/eve'
import { z } from 'zod'

export default defineTool({
  description: '按 ID 查找订单。',
  inputSchema: z.object({ orderId: z.string() }),
  async execute({ orderId }) {
    const order = await fetchOrder(orderId)
    const log = useLogger()
    log.set({ order: { id: order.id, amount: order.amount, status: order.status } })
    return order
  },
})
```

<callout color="neutral" icon="i-lucide-plug">

**解决方案：**当 AsyncLocalStorage 无法传播时（例如 hook 和工具使用了不同的 bundle），将 eve 工具的 `ctx` 传给 `useLogger(ctx)`。使用标准 eve agent 布局并注册了 `agent/hooks/evlog.ts` 后，通常无需传入 `ctx`。

`useLogger()` 从不抛出异常：当它无法访问轮次 logger 时，会发出一次警告，并返回一个接受所有调用但不产生任何输出的 logger。在步骤边界之后于另一个进程中恢复轮次是最常见的情况，丢失 enrich 信息绝不能导致正在进行 enrich 的工具失败。轮次会继续运行；只有其额外字段会被丢弃。

</callout>

### 4. Next.js Web 聊天（可选）

使用来自 `eve/next` 的 `withEve()` 包装 `next.config.ts`，并在应用中使用来自 `eve/react` 的 `useEveAgent()`。eve 会与 `next dev` 一起启动，并在同一源上代理 `/eve/v1/*`，因此工具调用、审批和 `ask_question` 都可以直接使用。请参阅[示例 agent](https://github.com/evloghq/evlog/tree/main/examples/eve)。

## 宽事件形态

每个已完成的轮次都会发出一个事件：

```json [宽事件 — 轮次等待审批]
{
  "method": "EVE",
  "path": "/sessions/sess_abc/turns/turn_0",
  "status": 200,
  "duration": "7.9s",
  "durationMs": 7903,
  "service": "clearbill-support-agent",
  "eve": {
    "sessionId": "sess_abc",
    "turnId": "turn_0",
    "turnSequence": 0,
    "phase": "awaiting-approval",
    "sessionTurns": 1
  },
  "customer": { "slug": "acme-corp", "plan": "enterprise" },
  "order": { "id": "4821", "amount": 890, "currency": "USD" },
  "approval": { "status": "pending", "tool": "issue_refund" },
  "ai": {
    "model": "deepseek/deepseek-v4-flash",
    "provider": "deepseek",
    "calls": 2,
    "steps": 2,
    "inputTokens": 11724,
    "outputTokens": 282,
    "finishReason": "tool-calls",
    "tools": [
      { "name": "lookup_customer", "durationMs": 26, "success": true, "inputTokens": 8900 },
      { "name": "lookup_order", "durationMs": 13, "success": true }
    ]
  }
}
```

在审批通过后，下一个轮次会携带结果：

```json [宽事件 — 退款已完成]
{
  "path": "/sessions/sess_abc/turns/turn_1",
  "eve": {
    "sessionId": "sess_abc",
    "turnId": "turn_1",
    "turnSequence": 1,
    "sessionTurns": 2
  },
  "refund": { "orderId": "4821", "amount": 890, "status": "refunded" },
  "audit": { "action": "refund.issued", "target": { "type": "order", "id": "4821" } },
  "approval": { "status": "approved", "tool": "issue_refund" },
  "ai": {
    "tools": [{ "name": "issue_refund", "durationMs": 993, "success": true }],
    "finishReason": "stop"
  }
}
```

Token 使用量和工具执行情况会从 eve 流事件（`step.completed`、`actions.requested`、`action.result`）中累积。通过 `useLogger()` 设置的业务字段会在同一会话的各轮次之间延续。在分析中使用 `eve.sessionId` + `eve.turnSequence` 关联各轮次，并且每个事件都保持自包含。`eve.phase` 仅会在非例行结束时设置：`awaiting-approval`、`awaiting-authorization`、`rejected`、`cancelled`、`failed`。

### 轮次可以报告的所有信息

除了上述标识符之外，轮次还会记录 eve 报告的相关信息。每个字段都是可选的，只有在轮次产生该字段时才会出现。

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      含义
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        eve.runtime
      </code>
    </td>
    
    <td>
      eve 版本、agent ID、模型，以及部署的 <code>
        gitSha
      </code>
      
       / <code>
        gitBranch
      </code>
      
       / <code>
        deployedAt
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.caller
      </code>
    </td>
    
    <td>
      触发轮次的调用方：<code>
        principalId
      </code>
      
      、<code>
        principalType
      </code>
      
       和 <code>
        authenticator
      </code>
      
      。在多用户频道中，你可以据此对成本和数量进行分组。绝不会记录 <code>
        subject
      </code>
      
       和 <code>
        attributes
      </code>
      
      （频道可能会将姓名或电子邮件放入其中）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.parent
      </code>
    </td>
    
    <td>
      子 agent 运行的父会话 ID 和根会话 ID。使用 <code>
        rootSessionId
      </code>
      
       重建委派树
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.authorizations
      </code>
    </td>
    
    <td>
      连接登录信息，包括其 <code>
        outcome
      </code>
      
      、<code>
        reason
      </code>
      
       和持续时间
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.compaction
      </code>
    </td>
    
    <td>
      执行了多少次压缩、使用了哪个模型，以及 <code>
        inputTokensAtTrigger
      </code>
      
      （第一次压缩触发时的上下文占用量）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.stepFailures
      </code>
      
       / <code>
        eve.failedSteps
      </code>
    </td>
    
    <td>
      失败的模型调用，包括随后通过重试成功的轮次中的失败调用
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.subagents
      </code>
    </td>
    
    <td>
      委派信息，包括其状态和持续时间
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.reasoning
      </code>
    </td>
    
    <td>
      <code>
        blocks
      </code>
      
       和 <code>
        chars
      </code>
      
      ，表示模型思考的规模，不包含思考内容
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.result
      </code>
    </td>
    
    <td>
      结构化结果，适用于具有输出 schema 的 agent
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        eve.contextCleared
      </code>
    </td>
    
    <td>
      持久化历史记录在此轮次期间被清除
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        message.responseChars
      </code>
    </td>
    
    <td>
      agent 响应的长度，无论使用哪种 <code>
        message
      </code>
      
       模式都会记录
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.costUsd
      </code>
    </td>
    
    <td>
      eve 报告的成本。<code>
        ai.estimatedCost
      </code>
      
       是根据 <code>
        cost
      </code>
      
       计算出的备用值
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.model
      </code>
      
       / <code>
        ai.provider
      </code>
    </td>
    
    <td>
      为轮次提供服务的网关 slug（<code>
        deepseek/deepseek-v4-flash
      </code>
      
      ）和解析后的 provider（<code>
        deepseek
      </code>
      
      ）。<code>
        ai.provider
      </code>
      
       是从 eve 在 <code>
        session.started
      </code>
      
       上报告的模型 ID 中拆分出来的
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ai.tools[].inputTokens
      </code>
    </td>
    
    <td>
      工具结果为下一次模型调用增加的输入 token 数，即调用工具的步骤与使用其结果的步骤之间的增量。同一步骤中的并行结果会均分该增量。如果轮次在结果被使用前结束，或压缩重置了上下文，则不设置该字段
    </td>
  </tr>
</tbody>
</table>

## 关联宽事件与追踪

一个宽事件和一个 Agent Runs span 描述的是同一轮交互，但它们不会自动关联。`defineEvlogInstrumentation()` 会将 evlog 的轮次标识写入 eve 的 AI SDK spans：

```typescript [agent/instrumentation.ts]
import { defineEvlogInstrumentation } from 'evlog/eve'

export default defineEvlogInstrumentation()
```

随后，每个模型调用 span 及其子 span 都会携带 `evlog.request_id` 和 `evlog.session_id`，其值与宽事件报告的 `requestId` 和 `eve.sessionId` 相同。你可以从 Braintrust、Datadog 或 Agent Runs 中的追踪直接跳转到 drain 中的事件，也可以反向跳转。

不使用 `setup` 时，OpenTelemetry 导出不会受到影响：eve 仍会写入本地追踪，可通过 `eve traces` 查看。传入 `setup` 即可将其导出到其他位置：

```typescript [agent/instrumentation.ts]
import { defineEvlogInstrumentation } from 'evlog/eve'
import { registerOTel } from '@vercel/otel'

export default defineEvlogInstrumentation({
  setup: ({ agentName }) => registerOTel({ serviceName: agentName }),
})
```

`functionId`、`recordInputs`、`recordOutputs` 和 `traceChannelRequests` 会直接传递给 eve 的 `defineInstrumentation`。

### 与其他可观测性后端并用

`defineEvlogInstrumentation()` 是仅使用 evlog 进行 agent instrumentation 时的快捷方式。它拥有该文件，而一个 agent 恰好只有一个 `agent/instrumentation.ts`，因此 eve 注册表中的每个可观测性项目都会写入同一个路径。这也是为什么在 `eve add instrumentation/posthog` 之后执行 `eve add instrumentation/sentry` 会拒绝执行，而不是覆盖前者。

一旦启用了其他后端，请使用 eve 自己的 `defineInstrumentation` 手动编写该文件，并将 `evlogRuntimeContext` 展开到运行时上下文中。evlog 会为你的 instrumentation 提供属性，而不是对其进行包装：

```typescript [agent/instrumentation.ts]
import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
import { PostHogTraceExporter } from '@posthog/ai/otel'
import { OTLPHttpProtoTraceExporter, registerOTel } from '@vercel/otel'
import { defineInstrumentation } from 'eve/instrumentation'
import { evlogRuntimeContext } from 'evlog/eve'

export default defineInstrumentation({
  // 每个后端使用一个 provider 和一个 span processor。生成
  // `traceExporter` 的项目（Sentry、Datadog、Arize、Jaeger、Braintrust、Honeycomb）
  // 会成为此数组中的一项。只调用一次 registerOTel，不要为每个后端各调用一次。
  setup: ({ agentName }) => registerOTel({
    serviceName: agentName,
    spanProcessors: [
      new SimpleSpanProcessor(new PostHogTraceExporter({
        projectToken: process.env.POSTHOG_PROJECT_TOKEN!,
      })),
      new BatchSpanProcessor(new OTLPHttpProtoTraceExporter({
        url: process.env.SENTRY_OTLP_TRACES_ENDPOINT!,
        headers: { 'x-sentry-auth': `sentry sentry_key=${process.env.SENTRY_PUBLIC_KEY}` },
      })),
    ],
  }),
  events: {
    'step.started': (input) => {
      const principalId = input.session.auth.initiator?.principalId
        ?? input.session.auth.current?.principalId
      return {
        runtimeContext: {
          ...evlogRuntimeContext(input),
          // 省略而不是留空：空属性会显示为空 ID。
          ...(principalId ? { posthog_distinct_id: principalId } : {}),
        },
      }
    },
  },
})
```

`evlogRuntimeContext` 在受追踪轮次之外会返回 `undefined`，展开它不会添加任何内容。PostHog 是唯一一个同时使用 `events` 的注册表项目；其余项目只需要它们的 span processor。在合并时，请保持每个生成的文件处于打开状态：环境变量和导出器选项是值得原样复制的部分。

跨度和宽事件通过属性而不是 trace ID 保持关联：agent turn 没有可继承的入站 `traceparent`，因此由 `evlog.request_id` 负责传递关联信息。

<callout color="warning" icon="i-lucide-triangle-alert">

**PostHog 会丢弃 `evlog.*`。**它的导出器会转发完整的 span，但服务端将其转换为 `$ai_generation` / `$ai_span` 事件时，只保留以 `posthog_` 为前缀的属性，并去掉该前缀。要在 PostHog 中进行关联，请在能够保留下来的名称下重复这些 ID，因为 `posthog_evlog_request_id` 会作为 `evlog_request_id` 到达。其他 OTLP 后端会原样接收 `evlog.*`。

</callout>

eve 只会将运行时上下文应用到步骤 span，因此其下的生成 span 不会继承任何内容。在 PostHog 中，这意味着成本会出现在没有身份和环境信息的事件上。需要使用一个 span processor，将当前 turn 的 `posthog_*` 属性复制到每个子 span 上，才能弥合这一缺口。

要反向完成关联，请将 agent 自身的宽事件发送到 PostHog Logs，并将 drain 指向同一个主体：

```typescript [agent/hooks/evlog.ts]
import { createPostHogDrain } from 'evlog/posthog'
import { defineEvlogHook } from 'evlog/eve'

export default defineEvlogHook({
  drain: createPostHogDrain({ distinctIdField: 'eve.caller.principalId' }),
})
```

这样，轮次就会进入与其生成的 LLM 追踪相同的 PostHog 用户。请参阅 [PostHog 适配器](https://evlog.zhcndoc.com/integrate/adapters/cloud/posthog#link-a-log-to-a-person-and-a-session-replay)。

## 生产环境

长时间运行的 eve agent 应关闭终端美化打印，并使用非阻塞的 drain：

```typescript [evlog.config.ts]
import { defineEvlog } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'

export default defineEvlog({
  service: 'my-agent',
  environment: 'production',
  pretty: false,
  sampling: { rates: { info: 10 } },
  drain: createDrainPipeline({ batch: { size: 50, intervalMs: 5000 } })(
    createAxiomDrain(),
  ),
})
```

```typescript [agent/hooks/evlog.ts]
import { defineEvlogHook } from 'evlog/eve'
import config from '../../evlog.config'

export default defineEvlogHook({ ...config, maxSessions: 256 })
```

<table>
<thead>
  <tr>
    <th>
      关注点
    </th>
    
    <th>
      建议
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      终端输出
    </td>
    
    <td>
      <code>
        pretty: false
      </code>
      
      ，因为美化打印仅适用于本地开发
    </td>
  </tr>
  
  <tr>
    <td>
      Drain 延迟
    </td>
    
    <td>
      使用批处理或异步 HTTP drain，绝不要让 I/O 阻塞轮次
    </td>
  </tr>
  
  <tr>
    <td>
      头部采样
    </td>
    
    <td>
      <code>
        sampling.rates
      </code>
      
      ，因为 eve 每个轮次只发出一个事件，而不是每个 token 发出一个事件
    </td>
  </tr>
  
  <tr>
    <td>
      内存
    </td>
    
    <td>
      <code>
        maxSessions
      </code>
      
      （默认 <code>
        256
      </code>
      
      ）会驱逐最久未使用的空闲会话状态
    </td>
  </tr>
  
  <tr>
    <td>
      负载
    </td>
    
    <td>
      Hook 处理器对每个流事件的复杂度为 O(1)，成本主要取决于 drain
    </td>
  </tr>
</tbody>
</table>

## 选项

`defineEvlogHook()` 接受 [`evlog.config.ts`](https://evlog.zhcndoc.com/cli/config) 中的所有运行时设置，以及下列 eve 选项。`map` 和 `logs` 仅供 CLI 使用，hook 会忽略它们。

<table>
<thead>
  <tr>
    <th>
      选项
    </th>
    
    <th>
      描述
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        service
      </code>
      
       / <code>
        environment
      </code>
      
       / <code>
        sampling
      </code>
      
       / <code>
        silent
      </code>
      
       / <code>
        redact
      </code>
    </td>
    
    <td>
      首次调用 hook 时应用的日志器设置；<code>
        service
      </code>
      
       默认为 <code>
        eve-agent
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drain
      </code>
      
       / <code>
        enrich
      </code>
      
       / <code>
        keep
      </code>
      
       / <code>
        plugins
      </code>
    </td>
    
    <td>
      与 HTTP 集成相同（<a href="https://evlog.zhcndoc.com/extend/plugins">
        插件
      </a>
      
      ）。<code>
        drain
      </code>
      
       和 <code>
        plugins
      </code>
      
       也会作用于轮次之外发出的事件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        init
      </code>
    </td>
    
    <td>
      原样传递给 <code>
        initLogger()
      </code>
      
      ，代替日志器设置；可让轮次之外发出的事件使用独立的 drain 或服务名称
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        enrichTurn
      </code>
    </td>
    
    <td>
      在轮次作用域中使用 eve 会话上下文（<code>
        ctx.session.auth
      </code>
      
      ）进行 enrich。每轮调用一次，返回字段会合并到轮次事件中
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        message
      </code>
    </td>
    
    <td>
      <code>
        'omit'
      </code>
      
      （默认）、<code>
        'preview'
      </code>
      
       或 <code>
        'full'
      </code>
      
      ，用于指定要记录多少用户消息和 agent 响应内容
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        messagePreviewLength
      </code>
    </td>
    
    <td>
      <code>
        'preview'
      </code>
      
       模式下保留的字符数（默认值为 <code>
        500
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sessionEvent
      </code>
    </td>
    
    <td>
      设为 <code>
        true
      </code>
      
       时，每个会话额外生成一个事件，汇总该会话的各轮交互
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        subagentEvents
      </code>
    </td>
    
    <td>
      设为 <code>
        false
      </code>
      
       时，如果子 agent 事件源没有 hook 会话上下文，则跳过其生命周期订阅
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cost
      </code>
      
       / <code>
        model
      </code>
    </td>
    
    <td>
      备用 token 定价（来自 <code>
        evlog/ai
      </code>
      
       的 <code>
        ModelCost
      </code>
      
      ），写入 <code>
        ai.estimatedCost
      </code>
      
      ，仅在 eve 未报告成本时使用
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        maxSessions
      </code>
    </td>
    
    <td>
      用于上下文传递的内存中会话上限（默认值为 <code>
        256
      </code>
      
      ）
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        include
      </code>
      
       / <code>
        exclude
      </code>
    </td>
    
    <td>
      对轮次路径（<code>
        /sessions/*/turns/*
      </code>
      
      ）应用类似路由的过滤器
    </td>
  </tr>
</tbody>
</table>

## 尾部采样

使用 `keep` 强制保留带有失败工具或高 token 使用量的轮次：

```typescript [agent/hooks/evlog.ts]
export default defineEvlogHook({
  drain: createAxiomDrain(),
  keep: (ctx) => {
    const ai = ctx.context.ai as {
      inputTokens?: number
      outputTokens?: number
      tools?: Array<{ success: boolean }>
    } | undefined
    const totalTokens = (ai?.inputTokens ?? 0) + (ai?.outputTokens ?? 0)
    if (totalTokens > 10_000) ctx.shouldKeep = true
    if (ai?.tools?.some(t => !t.success)) ctx.shouldKeep = true
  },
})
```

## 审计日志

结合 [审计日志](https://evlog.zhcndoc.com/use-cases/audit/overview)：将 `auditEnricher()` 作为 `enrich` 传入（或从自己的 `enrich` 中调用），并在人类审批门触发时在工具内调用 `log.audit()`。工具拒绝会在 `action.result` 中显示，`status: "rejected"`。

## 本地运行

```bash
git clone https://github.com/evloghq/evlog
cd evlog
pnpm install
pnpm example eve
```

`examples/eve` 项目是一个**客服退款助手**（Clearbill SaaS）：查询客户 → 查询订单 → 发起退款，当金额超过 100 美元时需要 eve 审批。运行 `pnpm example eve`，打开 **[http://localhost:3000**，然后点击一个起始提示。](undefined)

## 接下来阅读什么

- [eve hooks 指南](https://eve.dev/docs/guides/hooks)：流事件词汇
- [eve instrumentation](https://eve.dev/docs/guides/instrumentation)：OTel spans（互补功能）
- [AI SDK 用例](https://evlog.zhcndoc.com/use-cases/ai-sdk/overview)：当你直接拥有模型循环时
- [适配器概览](https://evlog.zhcndoc.com/integrate/adapters/overview)：drain 目标站点。

---

- [源代码](https://github.com/evloghq/evlog/tree/main/examples/eve)


## Sitemap

See the full [sitemap](https://evlog.zhcndoc.com/sitemap.md) for all pages.
