从 Curl 开始:不用 SDK,通过 DeepSeek API 手写 Agent Runtime

使用 cURL 实现 Agent 循环:深入探索 Memory 和知识库的差异

通过使用 cURL 来手动构造请求和响应,我们可以更深刻地理解智能代理(Agent)的工作原理。本文将带你从零开始构建一个完整的 Agent 循环,并在过程中探讨记忆(Memory)与知识库(Knowledge Base, KB)的区别。

1. 背景介绍

要成为有效的 Agent 开发人员,必须理解模型如何进行工具调用(Tool Calling),以及这些工具在实际操作中的执行方式。本文将通过几个步骤来逐步构建一个完整的循环流程,从最基础的对话开始到结构化 JSON 输出、再到工具调用闭环。

2. 准备阶段

安装依赖

为了进行实验,你需要安装 jq 工具来解析 JSON 数据,并确保你的环境中有可用的 cURL。此外,还需要准备好 API 访问令牌(API key),以通过 HTTPS 请求访问 DeepSeek 的聊天接口。

brew install jq  # macOS 上使用 Homebrew 安装
sudo apt-get install jq  # 在 Linux 系统上安装

创建工作目录和初始化文件

在你的本地机器上创建一个目录,用于存放请求响应的 JSON 文件和辅助工具。我们首先需要设置访问令牌,并创建几个基础的 JSON 文件。

mkdir -p agent_experiment/{requests,responses,kb,memory}
cd agent_experiment

3. 第一层:简单的对话

发送第一个请求

我们将从一个基本的聊天请求开始,使用以下脚本创建并发送第一个请求:

cat > requests/001_basic_chat.json <<'EOF'
{
    "model": "deepseek-chat",
    "messages": [
        {"role": "system", "content": "你是一个 AI 助手"},
        {"role": "user", "content": "你好,世界!"}
    ],
    "stream": false
}
EOF

curl -s https://api.deepseek.com/chat/completions \
     -H 'Content-Type: application/json' \
     -d @requests/001_basic_chat.json > responses/001_basic_response.json

分析响应

通过 jq 工具解析并查看返回的 JSON 数据:

jq . responses/001_basic_response.json

4. 第二层:结构化输出

在这一部分,我们将引导模型生成结构化的 JSON 输出,而不仅仅是自然语言文本。

发送请求获取格式化响应

更新 requests/002_structured_output.json 文件以明确要求模型输出 JSON 数据:

cat > requests/002_structured_output.json <<'EOF'
{
    "model": "deepseek-chat",
    "messages": [
        {"role": "system", "content": "你是一个结构化数据生成器,只返回合法的 JSON"},
        {
            "role": "user", 
            "content": "解释什么是 JSON 格式"
        }
    ],
    "response_format": {"type": "json_object"},
    "max_tokens": 150,
    "stream": false
}
EOF

curl -s https://api.deepseek.com/chat/completions \
     -H 'Content-Type: application/json' \
     -d @requests/002_structured_output.json > responses/002_structured_response.json

检查 JSON 结构并验证数据

解析生成的 JSON 文件,确保结构符合预期:

jq . responses/002_structured_response.json | jq .

5. 第三层:Tool Calling 的闭环流程

定义工具和创建知识库文件

首先定义一个具体的工具(例如 kb_search),然后在本地保存相关文件用于搜索。

cat > kb/search_terms.txt <<'EOF'
Agent Runtime 负责什么?
EOF

模拟 Tool Calling 过程

模拟 Agent 工作流中的 Tool Calling 阶段,具体包括发送请求、解析结果并进行工具调用等步骤:

# 发送包含工具定义的请求
curl -s https://api.deepseek.com/chat/completions \
     -H 'Content-Type: application/json' \
     -d @requests/003_tool_call_request.json > responses/003_response.json

# 处理返回结果,提取 tool_calls 并执行相应动作
jq '.choices[0].message.tool_calls' responses/003_response.json | jq ".[].function.arguments.query" | xargs grep -R .

# 将工具调用的结果反馈给模型以完成循环
curl -s https://api.deepseek.com/chat/completions \
     -H 'Content-Type: application/json' \
     -d @requests/004_tool_call_response.json > responses/004_final_answer.json

jq '.choices[0].message.content' responses/004_final_answer.json

6. 第四层:Memory 和 Knowledge Base 的区别

定义 Memory

在 Agent 工程中,memory 存储的是与特定项目或任务相关的长期状态信息。例如:

cat > memory/example_memory.jsonl <<'EOF'
{"type":"user_preference","key":"learning_style","value":"喜欢从 curl 和协议层开始理解,再翻译成代码","confidence":0.95}
{"type":"project_goal","key":"current_objective","value":"实现 Agent Runtime 的核心循环机制","confidence":1.0}
EOF

定义 Knowledge Base

另一方面,knowledge base 是一个更广泛的资源集合,通常包含可查询的文档或数据。例如:

cat > kb/agent_guide.md <<'EOF'
# 代理运行指南
...
EOF

结论

通过手动模拟 Agent 工作流中的各个步骤,我们能够更好地理解模型如何与外部工具互动以及如何构建复杂的自动化系统。同时,明确了 memory 和 knowledge base 的区别有助于开发更加高效和灵活的智能代理应用。

希望上述内容能为你提供实用的知识和实践指南,为深入研究 Agent 工程奠定坚实的基础。

7.3 内存与知识库的协同方式

每次请求之前,代理引擎需要执行以下步骤来构建上下文:

  1. 读取用户当前问题:首先从用户的输入中获取具体的问题或需求。
  2. 筛选相关事实:根据用户提问的内容,在内存存储(Memory)中检索相关的过往交互记录和已知信息。
  3. 检索知识库片段:接着,利用外部的知识库(Knowledge Base, KB),查找与当前问题直接相关的信息文档、规范和技术细节。
  4. 拼接成完整上下文:将从内存和知识库获取的数据合并为一个完整的请求上下文。
  5. 发送给模型处理:最后,构建好的请求体被传递给大语言模型进行进一步的解析和响应生成。

创建请求示例:

cat > requests/006_memory_kb_context.json <<'JSON'
{
  "model": "large-language-model",
  "messages": [
    {
      "role": "system",
      "content": "你是一个技术指导。请基于 MEMORY 和 KNOWLEDGE_BASE 回答,不要编造信息。"
    },
    {
      "role": "user",
      "content": "MEMORY:\n- 用户喜欢从协议层开始理解问题。\n- 目前的任务是从零实现代理运行时环境(Agent Runtime)。\n\nKNOWLEDGE_BASE:\n代理运行时负责模型调用、工具执行、上下文拼装等任务。\n\n问题:如何理解 MEMORY 和 KNOWLEDGE BASE 的协同作用?"
    }
  ],
  "stream": false
}

发送请求示例:

curl -s https://api.example.com/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d @requests/006_memory_kb_context.json \
  | tee responses/006_memory_kb_context.json

7.4 需要掌握的关键概念

  • 内存和知识库的管理:模型不会自动集成内存或知识库中的信息。代理引擎需要精心挑选、压缩这些数据,并将其整合到请求上下文中。

  • 上下文工程与检索策略

    • 上下文工程(Context Engineering)负责构建有意义的数据结构,用于后续的大语言模型交互。
    • 内存筛选(Memory Selection)涉及选择哪些过往信息对当前问题最有帮助。
    • 提示组装(Prompt Assembly)则是将所有相关信息按照逻辑顺序组合在一起。

8. 上下文缓存的最佳实践

DeepSeek API 的上下文缓存默认开启,但仅在请求具有重复前缀时生效。因此,在设计代理程序的运行流程时,建议遵循以下策略:

  • 稳定前缀定义:系统提示和角色规则是固定的。
  • 动态内容处理:内存记录、知识库中的检索片段以及用户的即时问题会不断变化。

推荐的上下文组织结构如下:

SYSTEM:
你是技术顾问。你的职责包括指导用户解决问题,提供详细的技术建议等。
记忆:
存储与当前请求相关的所有历史消息和对话。
知识模块:
包含从外部知识库获取的相关信息。

15. 最终工程目标

在掌握了使用curl构建代理交互流程后,您可以转向开发以下核心组件:

  • 模型适配器:处理API调用的细节。
  • 会话存储:管理对话历史记录。
  • 内存库与知识库检索模块:用于获取并组织上下文信息。
  • 工具注册表与执行者:确保可以安全地运行各种外部命令或服务,并将结果反馈给模型。
  • 审批策略及追踪日志系统:保证代理行为的安全性和可追溯性。

通过实现这些组件,您可以构建一个功能全面的代理环境,能够高效、准确地处理复杂的用户请求。