摘要:Latitude 是一个开源 AI 工程平台,优先实现观测性和评估,通过捕获生产流量中的提示、输入/输出、工具调用、延迟、令牌使用量和成本数据,建立评估驱动的可靠性循环,帮助团队从现有 LLM 调用开始,逐步迭代提示、版本管理、实验对比,并将失败转化为可重复的修复。平台采用 monorepo 架构、Drizzle ORM、BullMQ 作业、OpenTelemetry 追踪和 PromptL 链执行,支持云托管与自托管部署。本文基于官方文档,详细阐述贡献哲学、构建测试、Docker 生产构建、代码模式、数据库迁移、提示运行架构等实践,为专科及以上学历开发者提供准确、可操作的指导。

Latitude LLM:开源 AI 工程平台的架构设计与开发实践指南

你是否正在构建 LLM 驱动的应用,却苦于缺少系统化的观测、评估和持续优化机制?Latitude 提供了一个完整的开源解决方案。它不是简单的提示管理工具,而是围绕生产流量构建的工程平台:先通过观测捕获真实运行数据,再用评估量化质量,最后形成可靠性循环,将问题转化为可重复的修复。本文将带你一步步了解平台的整体设计、贡献流程和技术实现细节,帮助你高效参与或部署这个平台。

为什么选择 Latitude?

Latitude 的设计理念是分阶段采用:从观测和评估起步,逐步进入可靠性循环。

观测阶段:捕获真实流量中的提示模板、输入参数、输出结果、工具调用详情、延迟、令牌消耗和成本指标。这些数据直接来自生产环境,帮助你重现问题。

提示游乐场:使用真实输入重现运行过程,迭代提示内容、版本变更,并通过 AI 网关发布更新。

数据集管理:从生产日志中提取真实示例,构建批量测试集和回归测试套件。

评估系统:内置评估规则、LLM-as-judge 以及人工打分机制,支持实时评估。

实验功能:对比不同模型提供商、提示版本的效果,通过量化指标(如成功率、成本、延迟)选择最佳方案。

进入可靠性循环后:

  • 标注功能:将人工判断转化为可追踪的信号。
  • 问题发现:聚类失败案例,识别重复出现的故障模式。
  • 自动评估:将发现的问题转化为持续测试,守护每次发布。
  • 提示优化器(GEPA):在评估套件上搜索提示变体,降低重复失败率。

Latitude Telemetry 支持多数模型提供商和框架,可通过 OTLP 扩展自定义集成。这种渐进式路径让团队避免大刀阔斧的重构,从现有代码开始逐步提升可靠性。

快速开始 Latitude

Latitude 提供两种部署方式:云托管版和自托管版。

云托管版(Latitude Cloud)

  1. 在 latitude.so 注册账号并创建项目。
  2. 添加 Telemetry SDK 或导出 OTLP 追踪。
  3. 创建数据集和评估规则,测量质量并捕获回归。
  4. 版本化提示/代理,通过网关发布变更。
  5. 利用评估驱动优化减少重复失败。

自托管版
按照自托管生产部署指南安装后,重复上述云版步骤即可。平台支持 Docker Compose 快速启动数据库、Redis、Weaviate 等依赖。

平台核心优势在于观测数据的闭环利用:生产日志 → 数据集 → 评估 → 优化 → 重新部署,形成持续改进循环。

贡献哲学:长远思维与代码卫生

贡献 Latitude 时,请牢记核心理念:这个代码库将比你更长寿。任何快捷方式都会成为他人负担,任何 hack 都会积累技术债务,拖慢整个团队。

你不仅在编写代码,更在塑造项目未来。你的模式会被复制,你的捷径会被重复。必须对抗熵增,让代码库比你接手时更好。

具体要求:

  • 优先使用 TypeScript,倾向类型而非接口。
  • 采用函数式编程模式,早返回提升可读性。
  • 使用描述性命名,如 isLoading、hasError。
  • 事件处理函数以 handle 开头,例如 handleClick。
  • 目录名小写连字符,如 auth-wizard。
  • 避免枚举,使用 const 映射或类型联合。
  • 仅为导出的函数和类添加 JSDoc 注释,内部简单函数无需注释。
  • 导出置于文件顶部,内部方法置于底部。
  • 错误处理优先使用 instrumentation 的 captureException,而非 console.error。
  • 仅在需要 JSDoc 或被明确要求时添加注释。

这些规范确保代码库长期可维护,避免短期便利带来的长期成本。

构建与测试命令

项目使用 pnpm、Turborepo 和 Vitest 管理多包 monorepo。

常用命令:

  • pnpm build:构建所有包(仅在明确要求时使用)。
  • pnpm dev:启动开发服务器。
  • pnpm lint:检查所有包。
  • pnpm tc:类型检查所有包。
  • pnpm test:运行所有测试。
  • pnpm test:watch:监视模式运行特定包测试。
  • pnpm --filter @latitude-data/core db:migrate:运行数据库迁移。
  • pnpm --filter @latitude-data/core db:generate:生成迁移文件。
  • pnpm prettier:格式化代码。

测试时避免直接使用 vitest 命令,应通过 pnpm test 或在 package.json 添加相应脚本。

Docker 生产构建实践

生产部署采用 Docker Compose,支持三种配置文件:

  • docker-compose.yml:使用 GHCR 预构建镜像的生产部署。
  • docker-compose.local.yml:从源码本地构建开发镜像。
  • docker-compose.prod.yml:带 Traefik 反向代理的生产部署。

本地构建 Web 应用镜像

# 启动依赖(数据库、Redis、Weaviate)
docker compose -f docker-compose.local.yml up db redis weaviate -d

# 构建 web 容器
docker compose -f docker-compose.local.yml build web

# 运行 web 容器
docker compose -f docker-compose.local.yml up web

应用运行于 http://localhost:3000。

Web Dockerfile 关键配置

  • 基础镜像:node:22-alpine
  • 构建工具:Turbopack(next build –turbopack)
  • 输出模式:standalone(next.config.mjs 中设置 output: ‘standalone’)
  • 多阶段构建:Pruner → Builder → Runner,减少镜像体积

构建参数

  • NEXT_PUBLIC_*:客户端环境变量(编译时烘焙)
  • AWS_REGION、S3_BUCKET、BUILD_ID:静态资源上传至 S3
  • DD_GIT_COMMIT_SHA:Datadog source map 上传

测试构建镜像

docker run --rm --network llm_default --env-file .env -p 3000:8080 llm-web

或继续使用 docker compose。

其他服务构建:

  • docker compose -f docker-compose.local.yml build(全部)
  • docker compose -f docker-compose.local.yml build gateway workers websockets(指定服务)

代码风格与架构概述

项目采用 pnpm 工作区 + Turborepo monorepo 结构。

  • 核心业务逻辑位于 packages/core
  • UI 组件位于 packages/web-ui
  • 服务层采用函数式风格,返回 Result 抽象处理错误
  • 数据库操作使用 Transaction 抽象,模型位于 packages/core
  • 写入操作接收可选 db 参数,默认 database
  • 更新/删除服务接收模型实例而非 ID

测试文件与源码并列,使用 .test.ts 后缀。

测试模式详解

测试优先使用工厂函数,集成测试尽量减少 mock。

运行特定包测试
进入包目录后:

  • pnpm test -- "path/to/file.test.ts"
  • pnpm test -- "path/to/directory"
  • pnpm test(包内全部)

单元测试外部依赖模式

  1. 使用 vi.spyOn 模拟模块:
import * as cacheModule from '../../cache'
import * as diskModule from '../../lib/disk'

beforeEach(() => {
  vi.spyOn(diskModule, 'diskFactory').mockReturnValue(mockDisk as any)
  vi.spyOn(cacheModule, 'cache').mockResolvedValue(mockCache as any)
})
  1. 创建 vi.fn() mock 对象,定义所有使用方法。
  2. beforeEach 清空 mock 并重置。
  3. 测试 Result 对象:success 检查 ok 和 value,error 检查 ok 和 error.message。
  4. 覆盖边缘情况:缓存缺失、过期、外部服务失败、静默错误处理。
  5. 使用 @ts-expect-error 处理 mock 类型不匹配。

测试文件结构

import { beforeEach, describe, expect, it, vi } from 'vitest'

describe('moduleName', () => {
  const mockDependency = { method: vi.fn() }

  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('functionName', () => {
    it('describes expected behavior', async () => {
      // Arrange
      mockDependency.method.mockResolvedValueOnce(value)
      // Act
      const result = await functionUnderTest(args)
      // Assert
      expect(result).toEqual(expected)
      expect(mockDependency.method).toHaveBeenCalledWith(expectedArgs)
    })
  })
})

这种结构使测试清晰、可维护,并全面覆盖成功、失败和边缘路径。

CRUD 操作模式

服务层(packages/core/src/services/)

  • 每个实体创建独立文件夹,如 apiKeys/、providerApiKeys/
  • 文件:create.ts、destroy.ts、update.ts
  • 使用 Transaction 和 Result,返回命名导出
  • 避免 barrel 文件导出全部服务

动作层(apps/web/src/actions/)

  • 使用 authProcedure + Zod 验证
  • 先通过仓库获取模型实例,再调用服务
  • 管理后台动作置于 actions/admin/

示例:

export const updateApiKeyAction = authProcedure
  .inputSchema(z.object({ id: z.number(), name: z.string() }))
  .action(async ({ parsedInput, ctx }) => {
    const repo = new Repository(ctx.workspace.id)
    const model = await repo.find(parsedInput.id).then((r) => r.unwrap())
    return updateService(model, { name: parsedInput.name }).then((r) =>
      r.unwrap()
    )
  })

项目作用域示例使用 withProject 过程。

存储层(apps/web/src/stores/)

  • 使用 SWR 自定义钩子
  • 动作成功后乐观更新
  • 导出 create、destroy、update 及 loading 状态
  • 成功/错误时显示 toast

UI 模式:使用模态框编辑,表格单元格按钮带 tooltip,图标统一(edit、trash)。

数据库模式与迁移

使用 Drizzle ORM + PostgreSQL。

模式定义(packages/core/src/schema/models/)

export const tableName = latitudeSchema.table('table_name', {
  id: bigserial('id', { mode: 'number' }).notNull().primaryKey(),
  name: varchar('name', { length: 256 }).notNull(),
  workspaceId: bigint('workspace_id', { mode: 'number' })
    .notNull()
    .references(() => workspaces.id, { onDelete: 'cascade' }),
  ...timestamps(),
})

破坏性迁移要求(两步 PR):
PR1(先部署):移除代码引用、更新 Drizzle 模式、仓库、服务、工厂、测试。旧列仍存在但未使用。
PR2(后部署):生成并运行迁移删除未用列/表。

仓库模式
继承 RepositoryLegacy 或独立类,实现 scope getter 过滤 workspace。

ClickHouse 迁移

ClickHouse 用于分析和高性能存储,迁移使用 golang-migrate。

命令:

  • pnpm --filter @latitude-data/core ch:connect
  • pnpm --filter @latitude-data/core ch:status
  • pnpm --filter @latitude-data/core ch:create <name>
  • pnpm --filter @latitude-data/core ch:up
  • pnpm --filter @latitude-data/core ch:down [N|all]
  • pnpm --filter @latitude-data/core ch:reset

两个迁移文件夹

  • unclustered/:单节点(开发、自托管)
  • clustered/:复制集群(生产)

必须为每个迁移同时创建并保持同步两种版本。

unclustered 示例

CREATE TABLE events (
  id String,
  workspace_id UInt64,
  timestamp DateTime64(3)
) ENGINE = ReplacingMergeTree()
ORDER BY (workspace_id, timestamp, id);

clustered 示例

CREATE TABLE events ON CLUSTER default (
  id String,
  workspace_id UInt64,
  timestamp DateTime64(3)
) ENGINE = ReplicatedReplacingMergeTree()
ORDER BY (workspace_id, timestamp, id);

规则:

  • 始终创建两种版本
  • 逻辑完全一致,仅语法差异
  • 提供可逆 down.sql
  • 使用 ReplacingMergeTree 支持 upsert
  • 查询包含 workspace_id 实现租户隔离

API 路由模式

路由置于 apps/web/src/app/api/[endpoint]/route.ts

使用 errorHandler(authHandler(…)) 保护路由。

示例:

export const GET = errorHandler(
  authHandler(
    async (_: NextRequest, { workspace }: { workspace: Workspace }) => {
      const repo = new Repository(workspace.id)
      const data = await repo.findAll().then((r) => r.unwrap())
      return NextResponse.json(data, { status: 200 })
    }
  )
)

公共路由仅用 errorHandler。

作业模式(BullMQ)

作业用于后台任务,如导出、评估、文档运行。

要求:

  • 成功时返回 undefined
  • 可重试错误直接 throw
  • 不可重试错误使用 captureException
  • 配置 removeOnComplete: true,removeOnFail: false
  • 在 packages/core/src/jobs/index.ts 注册

错误处理示例:

  • throw 触发重试
  • captureException 处理单个项失败但作业继续

后台管理(Backoffice)模式

  • 添加路由至 BackofficeRoutes 枚举和 ROUTES.backoffice
  • 页面置于 app/(admin)/backoffice/[section]/page.tsx
  • 使用 Text.H1 标题、Text.H4 描述
  • 组件置于 _components/ 子目录
  • 避免 barrel 导出

UI 规范:

  • 从 @latitude-data/web-ui/atoms/ 导入
  • 使用 Modal、TextArea
  • 按钮 size=’small’
  • 表格组件:Table、TableBody 等
  • 读操作用 API 路由 + useFetcher + SWR
  • 写操作用 server actions + useLatitudeAction

特征实现检查清单

新功能按以下顺序:

  1. 数据库模式 + 生成迁移
  2. 服务层(Result 模式)
  3. 仓库层
  4. 动作层(admin/ 用于后台)
  5. API 路由
  6. 存储层(SWR 乐观更新)
  7. UI 组件
  8. 路由与导航

SDK 发布流程(TypeScript SDK)

修改后必须:

  1. 更新 packages/sdks/typescript/package.json 版本(semver)
  2. 更新 CHANGELOG.md(Added/Changed/Fixed 等节)
  3. 推送到 main 触发 CI/CD:构建、npm 发布、GitHub release、打标签

作业与事件系统

事件声明

  • 添加至 Events 联合类型
  • 定义 LatitudeEventGeneric
  • 添加至 LatitudeEvent
  • (可选)添加至 IEventsHandlers

发布
使用 publisher.publishLater 或 publish。

事件处理:在 packages/core/src/events/handlers/index.ts 注册。

提示运行架构详解

提示运行系统支持前台流式和后台队列执行。

高层流程
API 请求 → Gateway → runDocumentAtCommit → ChainStreamManager → AI Provider → 响应
同时支持后台 enqueueRun → BullMQ 作业,并记录 OpenTelemetry 追踪。

核心服务

  • runDocumentAtCommit:构建提供商映射、解析内容、校验链、委托 runChain。
  • ChainStreamManager:递归 step 执行、工具解析、流式响应。
  • ai() 服务:应用提供商规则、创建 Vercel AI SDK 适配器、streamText。

后台执行
enqueueRun 创建 Redis 记录并入队,backgroundRunJob 处理作业、启动追踪、转发流事件至 Redis stream。

追踪
使用 OpenTelemetry,Span 类型包括 Prompt、Completion、Tool、Step 等。
Span 处理包含 prompt 参数、模板、引用等元数据。

评估集成
spanCreated 事件触发 evaluateLiveLogJob,对符合条件的日志运行评估,生成 EvaluationResultV2。

PromptL 链
编译为 Chain 对象,支持 step() 逐步执行,处理 userMessage、参数验证、文件类型转换。

工具处理
支持客户端工具、内置工具、MCP 集成、代理子提示,通过 lookupTools 和 resolveTools 解析。

这种架构确保生产级可观测性、可扩展性和错误恢复能力。

FAQ

Q: Latitude 如何处理破坏性数据库变更?
A: 分两步 PR。第一步移除代码引用并更新模式,部署后第二步生成并运行删除迁移,避免旧代码崩溃。

Q: 如何为新功能添加评估?
A: 在 evaluationsV2/specifications/ 实现 run() 和 supportsLiveEvaluation,支持 LLM-as-judge、规则或自定义指标。Live 评估通过 spanCreated 事件自动触发。

Q: 后台作业错误如何重试?
A: 可重试错误直接 throw 让 BullMQ 重试,不可重试错误使用 captureException 记录但继续执行。

Q: ClickHouse 迁移需要注意什么?
A: 必须同时维护 unclustered 和 clustered 两个版本,仅 ON CLUSTER 和引擎前缀不同。使用 ReplacingMergeTree 并包含 workspace_id。

Q: 如何在 UI 中实现乐观更新?
A: useLatitudeAction 的 onSuccess 内调用 mutate 更新 SWR 数据,同时显示 toast 反馈。

Q: 提示链如何支持工具调用?
A: ChainStreamManager 通过 resolveTools 准备工具,streamAIResponse 处理请求/完成事件,支持多步链执行。

Q: 自托管时如何选择 ClickHouse 模式?
A: 根据 CLICKHOUSE_CLUSTER_ENABLED 环境变量自动选择 unclustered 或 clustered 迁移文件夹。

通过以上实践,Latitude 构建了一个可观测、可评估、可优化的 LLM 工程平台。无论你是贡献代码还是部署生产,都能从中获得清晰的路径和可靠的工具支持。欢迎加入社区,一起推动 AI 工程化发展。