Skip to content
签到签到

第 14 章:最小 Agent 与面试叙事

本章解决的问题

最后一步是把前面概念变成可运行代码和可讲的项目故事。

交互图解:一次 Agent Turn 完整调用链 · 查看图解导读

大白话理解

你不需要实现 DeepSeek Harness 的全部能力。只要实现一个最小闭环,就能理解 Agent 的核心。

最小闭环:

text
Context
-> ToolRegistry
-> SessionLog
-> LlmPort
-> AgentLoop
-> React UI

必须掌握的概念

1. LlmPort

模型能力应该抽象成接口,不要直接绑定某个供应商。

2. ToolRegistry

工具注册、Schema 和 execute 要集中管理。

3. SessionLog

用只追加日志保存消息和工具结果。

4. AgentLoop

AgentLoop 负责把输入、模型、工具和日志串起来。

5. UI

React UI 负责输入、流式展示和工具状态。

DeepSeek Harness 对应实现

DeepSeek Harness 的最小组成可以简化为:

  • ctx.sessions
  • ctx.systemPrompt
  • ctx.tools
  • ctx.agents
  • ctx.agentLoop
  • ctx.llm

你不必复刻 Cordis,但可以复刻这些抽象。

进一步阅读:

  • packages/core/README.md
  • docs/architecture.md

代码示例

最小 TypeScript Harness

ts
type Role = 'user' | 'assistant' | 'tool'

interface Message {
  role: Role
  content: string
}

interface Tool {
  name: string
  description: string
  run(args: string): Promise<string>
}

interface LlmPort {
  complete(messages: Message[], tools: Tool[]): Promise<{
    text: string
    toolCall?: { name: string; args: string }
  }>
}

class ToolRegistry {
  private tools = new Map<string, Tool>()

  register(tool: Tool): void {
    this.tools.set(tool.name, tool)
  }

  list(): Tool[] {
    return [...this.tools.values()]
  }

  async run(name: string, args: string): Promise<string> {
    const tool = this.tools.get(name)
    if (!tool) throw new Error(`unknown tool "${name}"`)
    return tool.run(args)
  }
}

class SessionLog {
  private messages: Message[] = []

  append(message: Message): void {
    this.messages.push(message)
  }

  all(): Message[] {
    return [...this.messages]
  }
}

class AgentLoop {
  constructor(
    private llm: LlmPort,
    private tools: ToolRegistry,
    private log: SessionLog,
  ) {}

  async run(task: string): Promise<string> {
    this.log.append({ role: 'user', content: task })

    for (let step = 0; step < 8; step += 1) {
      const reply = await this.llm.complete(this.log.all(), this.tools.list())
      this.log.append({ role: 'assistant', content: reply.text })

      if (!reply.toolCall) return reply.text

      const result = await this.tools.run(reply.toolCall.name, reply.toolCall.args)
      this.log.append({ role: 'tool', content: result })
    }

    return 'max steps reached'
  }
}

最小 React 页面

tsx
import { useState } from 'react'

function AgentPage({ agent }: { agent: { run(task: string): Promise<string> } }) {
  const [task, setTask] = useState('')
  const [answer, setAnswer] = useState('')

  async function submit() {
    setAnswer('')
    const result = await agent.run(task)
    setAnswer(result)
  }

  return (
    <div>
      <textarea value={task} onChange={event => setTask(event.target.value)} />
      <button onClick={submit}>运行</button>
      <pre>{answer}</pre>
    </div>
  )
}

面试怎么问

问:如果让你设计一个前端 AI Agent 平台,你会怎么开始?

回答要点:

  • 先拆出模型层、工具层、会话层和 UI 层。
  • 模型层用接口隔离供应商。
  • 工具层用注册表和策略管线。
  • 会话层用事件日志。
  • UI 层处理流式、工具状态和权限。

问:你如何证明自己理解 Agent,而不只是用过 API?

回答要点:

  • 展示自己实现的 Agent Loop。
  • 说明如何回传工具结果。
  • 说明如何处理取消、流式和错误。
  • 说明事件日志如何支持恢复和审计。

问:3 到 5 个月怎么规划学习?

回答要点:

  • 第 1 个月:LLM、流式、ReAct、工具调用。
  • 第 2 个月:DeepSeek Harness 架构和 Cordis。
  • 第 3 个月:React-free 对象层、快照、前端流式 UI。
  • 第 4 个月:RAG、MCP、安全、持久化。
  • 第 5 个月:最小项目、面试题和项目叙事。

自测题

  1. 最小 Agent Harness 需要哪五类抽象?
  2. LlmPort 的作用是什么?
  3. 工具结果如何回传给模型?
  4. 你的项目叙事如何体现前端价值?

本章小结

完成一个最小 Agent 后,你已经具备回答面试问题的基础:不是“我会调 API”,而是“我理解 Agent 的循环、工具、记忆、权限和前端状态设计”。

进一步阅读:

  • docs/cordis-tutorial/07-into-the-harness.md
  • docs/cookbook/adding-a-tool.md