使用C#从零开发Agent系列教程 第一课:认识LLM API

Agent

前言

从今天开始开个新坑,讲一讲Agent的开发。Agent开发的本质和原理都是非常简单直接的,但市面上,尤其是中文系列教程,都写的太烂了。

我们这个系列教程的最终目的,是基于DeepSeek API,用C#写出一个类似于Claude Code或Copilot CLI的工具。选择DeepSeek API的原因比较简单:便宜,好用。而选C#的原因则有点猎奇:一方面是如今我最熟悉的编程语言就是C#,另一方面,市面上现在有了太多的基于Python或前端技术栈的Agent开发教程,搞得仿佛Agent开发就得用Python或者NodeJS一样,这是不对的。

实际上,Agent开发用什么语言都可以,而之所以市面上大多数的Agent工具都是用Python或者NodeJS实现的,原因只是Agent本身并不是性能敏感的东西,并且Python和NodeJS这玩意写起来飞快。这个随着后续你跟进这个教程你就会体会到。

好了,废话不多说,我们开始旅程吧。

1. 准备工作

首先,你先去deepseek开放平台去注册个账号,然后充点钱,我建议你先充个二三十,如果你财力雄厚,就多充点,充个百八十块的。

然后我们需要创建一个API KEY,如图,注意这个API KEY就跟密码一样,它只会在这个页面显示一次,所以注意复制保存一下:

create_deep_seek_api_key

然后把这个玩意设置成你的环境变量

set_env_var_01 set_env_var_02

现在,你在powershell中就能读到这个环境变量的值了,在本系列教程后续所有要用到API KEY的地方,我们都是去环境变量里去读这个值的:

read_env_var_in_pwsh

再接下来h,去bruno的官网下载bruno安装上,这个软件只是一个用来发送http请求的小工具,和postman差不多,如果你已经安装postman并且知道postman怎么使用的话,可以不安装bruno。

2. 简单体验一下API CALL和网页对话的区别

现在我们打开bruno,然后创建一个请求,如下所示:

setup_bruo_01 setup_bruo_02 setup_bruo_03 setup_bruo_04 setup_bruo_05 setup_bruo_06

先不纠结http request/response的json格式,直观上能感受到,这个api请求调用得到的结果好像有点不对劲,deepseek api并没有查出来今天的日期,以及上证指数到底是多少。而作为对比,在网页版 deepseek上,回答就智能了很多:

deepseek_web_01

而更神奇的是,如果我们改一下请求体里的问题,继续追问deepseek api的话,api仿佛失忆了一样:

setup_bruo_07

而网页版的deepseek显然是能多轮对话的,并且有一定的记忆力。

这就奇了怪了,怎么花钱的网页版看起来比花钱的api请求好用呢?付费玩家怎么还获得更差的体验了呢?

3. LLM API和网页产品的区别

要理解为什么API调用效果那么差,就必须纠正一下你脑海中对于AI、大语言模型、chatgpt、openai、deekseek的错误认识。

接下来我用极不严谨,甚至可能包含错误的解释,给你讲一讲这里面的门道,注意,虽然里面可能包含一些错误,但至少能帮你建立起“稍微正确”一点的一些sense。我们来讲个故事:

几年前,有一帮聪明人,通过力大砖飞的原理,“炼”出了一个牛逼的程序。这个程序,以及同类的后来的相似的程序,就是我们今天所的“大语言模型”,英文名叫large language model,简称LLM,也有说法叫这类程序为“模型”,Model。

之所以说这个程序,这些程序,是“炼”出来的,而不是“写”出来的,是因为LLM的制造过程,跟传统程序不一样:传统程序是程序员哼哧哼哧996加班写代码,然后编译生成的。

这玩意可不是,这玩意是一帮特殊的程序员先哼哧哼哧写一堆特殊代码,生成一个特殊的程序,这个特殊程序甚至不是跑在CPU上的,而是跑在显卡上的。然后再拿海量的文本数据灌进这个程序里,等个七七四十九天,显卡上跑这个程序把海量的文本处理完了,最终生成的一个怪胎。这就是LLM。

LLM这玩意牛逼在哪呢?你可以把它想象成一个一问一答的对话程序,它让人毛骨悚然的是,跟它对话,仿佛是在跟一个有思维的“人”对话。

但LLM本身有几个缺点:

  1. 它没有记忆,在一问一答的过程中,它没法保存之前的对话。。于是人类想了个折中的办法,就是每次跟它说话的时候,一股脑先把之前的聊天记录也一并交给它。这样“模拟”出了LLM有脑子,有记忆的假象
  2. 它的输出是不稳定的:同样的问题,你问它十次,它能吐出十次不同的结果
  3. 它很难学习新东西:它所有的知识,都只局限于当初在GPU上“炼”它的时候,那帮邪修程序员灌给它的内容。
  4. 它的输入是文本,输出也是文本。它啥也干不了,它没长手,它唯一能做的,就是“聊天”。(后续有多模态的模型可以处理和输出音视频图像等,这里不抬扛)
  5. 这玩意非常费电,运行成本巨大,得跑在数据中心里,一跑起来电表都转成陀螺了。

但LLM确实有以下几个让人毛骨悚然的优点:

  1. 炼这玩意的时候,那帮邪修程序员是真的拿海量的数据去炼它的。所以它的知识虽然不更新,它炼进它脑子的知识,它都知道,它知识面巨广。真是上知天文下知地理,它能算数学题,还知道建筑设计规范,也知道东欧巨变苏联解体,还能背唐诗宋词。
  2. 它跟人不一样,它虽然没有“记忆”,但它一次能接收非常多的文本数据作为输入。刚开始时是几万字,现在已经发展到一次性能听你叨叨几十万字了。这也是为什么我们能“模拟”对话上下文的原因。

好了,我们就暂时理解到这里就行了,现在假想我就是第一批炼出LLM的那帮邪修程序员之一,你怎么解决如下三个问题?

  1. 它的知识不更新
  2. 它完全没有记忆
  3. 它只能接收文本,吐出文本,做不了其它事

第二个问题怎么解决我们刚才已经提过了,但第一个和第三个问题怎么解决呢?

解决方法也非常朴素,就是把它想象成你那个屁都不懂只会指挥的老板,然后思考你老板是怎么做事的:

对于问题3:老板只动嘴皮子,但会指挥员工去做事。

但前提是:老板得知道哪些员工会做哪些事,这样他才能正确的指挥。

对于问题1:老板知识面广,但新鲜事物是完全不懂,所以你跟老板汇报的时候,你得告诉老板:“王总,虽然你不懂怎么做饭,但我知道有个B站up主叫王刚,他是个专业厨子,你看这个视频就知道怎么炒肉不沾锅了”

但前提是,你还得给老板再配个“会看视频,然后总结视频内容,最后把炒肉如何不沾锅的技巧总结出来总结成文本”的员工。

简单来说,你想让你老板给你做个辣椒炒肉,这事得这么办:

  1. 给老板说以下内容:
    “老板,我想吃辣椒炒肉,你给我炒一盘”
    “我知道你只会动嘴,所以我这里给你配备了个炒菜员工”
    “我还给你配备了一个买菜员工,他会你的要求去菜市场买菜”
  1. 老板沉吟片刻,做出指挥:
    “根据我的知识,辣椒炒肉需要的食材包括辣椒、肉、葱姜蒜、油盐酱醋”
    “我得先解决食材问题,刚好有个员工能去买菜,我得先让他去买上面提到的这些食材”
  1. 收到老板的回应后,小王去菜市场买齐了辣椒、肉、葱姜蒜、油盐酱醋。并且考虑到老板完全没有任何记忆,第二次给老板说的话,就得先包含上一轮对话的聊天记录:
第一轮我说:
    “老板,我想吃辣椒炒肉,你给我炒一盘”
    “我知道你只会动嘴,所以我这里给你配备了个炒菜员工”
    “我还给你配备了一个买菜员工,他会你的要求去菜市场买菜”
第一轮你回:
    “根据我的知识,辣椒炒肉需要的食材包括辣椒、肉、葱姜蒜、油盐酱醋”
    “我得先解决食材问题,刚好有个员工能去买菜,我得先让他去买上面提到的这些食材”
第二轮我说:
    “小王已经按你的要求买齐了所有食材”
  1. 老板再次沉吟片刻,做出指挥:
    “小李,开始炒菜”
  1. 然后小李开始炒菜,结果发现肉炒起来粘锅,炒失败了。于是要反馈老板,并且考虑到老板完全没有任何记忆,我们还得把所有历史信息全打包再次发给老板:
第一轮我说:
    “老板,我想吃辣椒炒肉,你给我炒一盘”
    “我知道你只会动嘴,所以我这里给你配备了个炒菜员工”
    “我还给你配备了一个买菜员工,他会你的要求去菜市场买菜”
第一轮你回:
    “根据我的知识,辣椒炒肉需要的食材包括辣椒、肉、葱姜蒜、油盐酱醋”
    “我得先解决食材问题,刚好有个员工能去买菜,我得先让他去买上面提到的这些食材”
第二轮我说:
    “小王已经按你的要求买齐了所有食材”
第二轮你回:
    “小李,开始炒菜”
第三轮我说:
    “小李炒菜过程中发现沾锅沾的要死”
    “不过恰好我知道B站有个up主叫王刚,他分享了很多做菜视频,我们做菜失败的话可以去他的视频里找找原因,小张可以去看视频分析原因”
  1. 老板再再次沉吟片刻,做出指挥:
    “小张,去分析一下炒肉为什么沾锅”
  1. 然后小张哼哧哼哧看完了王刚的八百个视频,总结出了解决沾锅问题的办法,再次报告老板,并并并并并且考虑到老板没有记忆:
第一轮我说:
    “老板,我想吃辣椒炒肉,你给我炒一盘”
    “我知道你只会动嘴,所以我这里给你配备了个炒菜员工”
    “我还给你配备了一个买菜员工,他会你的要求去菜市场买菜”
第一轮你回:
    “根据我的知识,辣椒炒肉需要的食材包括辣椒、肉、葱姜蒜、油盐酱醋”
    “我得先解决食材问题,刚好有个员工能去买菜,我得先让他去买上面提到的这些食材”
第二轮我说:
    “小王已经按你的要求买齐了所有食材”
第二轮你回:
    “小李,开始炒菜”
第三轮我说:
    “小李炒菜过程中发现沾锅沾的要死”
    “不过恰好我知道B站有个up主叫王刚,他分享了很多做菜视频,我们做菜失败的话可以去他的视频里找找原因,小张可以去看视频分析原因”
第三轮你回:
    “小张,去分析一下炒肉为什么沾锅”
第四轮我说:
    “解决沾锅的要点在于热锅凉油,并且肉下锅后保持不翻动,等定型后再翻炒”
    
  1. 老板做出指示:
    “小李,重新炒菜,注意这次要热锅凉油,并且你那个逼手不要那么贱,肉刚下锅不要急着翻炒”
  1. 然后小李重新炒菜
第一轮我说:
    “老板,我想吃辣椒炒肉,你给我炒一盘”
    “我知道你只会动嘴,所以我这里给你配备了个炒菜员工”
    “我还给你配备了一个买菜员工,他会你的要求去菜市场买菜”
第一轮你回:
    “根据我的知识,辣椒炒肉需要的食材包括辣椒、肉、葱姜蒜、油盐酱醋”
    “我得先解决食材问题,刚好有个员工能去买菜,我得先让他去买上面提到的这些食材”
第二轮我说:
    “小王已经按你的要求买齐了所有食材”
第二轮你回:
    “小李,开始炒菜”
第三轮我说:
    “小李炒菜过程中发现沾锅沾的要死”
    “不过恰好我知道B站有个up主叫王刚,他分享了很多做菜视频,我们做菜失败的话可以去他的视频里找找原因,小张可以去看视频分析原因”
第三轮你回:
    “小张,去分析一下炒肉为什么沾锅”
第四轮我说:
    “解决沾锅的要点在于热锅凉油,并且肉下锅后保持不翻动,等定型后再翻炒”
第四轮你回:
    “小李,重新炒菜,注意这次要热锅凉油,并且你那个逼手不要那么贱,肉刚下锅不要急着翻炒”
第五轮我说:
    “小李炒成功了,我吃上辣椒炒肉了!”
  1. 然后老板做出最终指示:
    “肉炒成功了,也吃上了,这个项目完成了,结束 ”

这个例子很啰嗦,但基本逻辑是对的,在看明白这个例子后,我们再来介绍以下概念:

  1. LLM无状态,无记忆的
  2. 基于LLM的连续对话产品,实现的谓的“记忆”,靠的就是在每一次调用LLM时,把历史聊天记录全附上
  3. 上面例子中员工,就是解决问题3的办法,它在API请求中,叫Tool。Tool是提供给LLM API的额外信息,也是告诉LLM,当要做的事超出LLM能力范围时,让LLM自己分析合适的Tool去指挥调用。可以把Tool理解为函数。更要理解到:LLM听到的只是Tool能干什么,LLM指挥的只调用Tool时应该怎么传递参数,传递哪些参数
  4. 实际真正指挥员工做事的,其实是“我”,也是Agent。我负责把老板的指挥转译成真正的,对员工/函数的调用,并且把调用结果再反馈给LLM。

所以,至此,想必你也理解了,为什么Deepseek API和网页产品区别那么大。

4. 到底是谁在做辣椒炒肉?

下面,则是我们对上面那个例子的生动复刻,唯一跟我们的例子对不太上的是,deepseek api并不需要小张去分析为什么炒肉会粘锅,deepseek api自己知道:

首先,我们发送如下请求。虽然请求体比较啰嗦,数据格式稍显复杂,但比较直观,你一眼就能看明白。里面有个小点,就是tool/function的名字只能由英文及数字和下划线组成,所以我们用了xiao_wang代表小王,xiao_li代表小李。

{
  "model": "deepseek-flash",
  "messages": [
    {
      "role": "user",
      "content": "我想吃辣椒炒肉"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "xiao_wang",
        "description": "小王可以去菜市场购买东西,包括肉蛋奶葱姜蒜油盐酱醋等常见食材与调料,以及各种烹饪工具与锅碗瓢盆",
        "parameters": {
          "type": "object",
          "properties": {
            "物品": {
              "type": "string",
              "description": "要购买的物品名称"
            }
          },
          "required": ["物品"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "xiao_li",
        "description": "小李是个厨子,会炒菜,但炒辣椒炒肉时需要额外技巧才能不沾锅,否则辣椒炒肉会制作失败",
        "parameters": {
          "type": "object",
          "properties": {
            "菜名": {
              "type": "string",
              "description": "要炒的菜的名称"
            },
            "额外技巧": {
              "type": "string",
              "description": "做菜时的额外技巧"
            }
          },
          "required": ["菜名"]
        }
      }
    }
  ]
}

我们收到如下回应,看来deepseek还是非常严谨,它一方面成功的理解了我们的需求,另一方面很严谨的分析出了,我们需要先购买辣椒、猪肉、大蒜、生姜、食用油、盐、生抽、淀粉等物品。到这一步,站在LLM的角度来看,它是在指挥小王去买菜。

{
  "id": "2516d9eb-4630-4f60-a11e-3dbdea93605a",
  "object": "chat.completion",
  "created": 1792227518,
  "model": "deepseek-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "我这就准备食材:辣椒、猪肉和常用配料,再用防粘技巧来炒。",
        "reasoning_content": "We need respond in Chinese. Need accomplish: buy ingredients via xiao_wang, then ask xiao_li cook with extra technique to avoid sticking. Need know \"额外技巧\" required. The description says 小李 needs extra technique otherwise dish fails. We need probably buy ingredients including 辣椒, 猪肉, etc, then call cook with technique. Could do parallel? Buying and cooking dependent—need ingredients first. But can buy all at once. Tool only one item per call. We can parallel multiple xiao_wang calls for ingredients, then xiao_li call after. Need infer common ingredients: 猪肉、辣椒、葱姜蒜、油盐酱醋 maybe. Could buy \"辣椒炒肉的食材\" but item name maybe accepted? Better buy pork and peppers, plus seasonings. They asked \"想吃辣椒炒肉\". We can buy 猪肉, 辣椒, 蒜, 姜, 葱, 生抽, 老抽? 淀粉? 油盐酱醋. Overkill. Need perhaps ingredient list: 五花肉/前腿肉、青椒、蒜、姜、豆豉? The task expects use xiao_wang to purchase ingredients and xiao_li cook. Since \"额外技巧\" important: 热锅凉油? For 辣椒炒肉 not sticking: \"先把锅烧热,用油滑锅,再下肉\" or \"热锅凉油、先煸辣椒再炒肉\". Need include. Let's call buy items in parallel. Then cook. Need maybe no need ask preference. Let's do.",
        "tool_calls": [
          {
            "index": 0,
            "id": "call_00_sADDPPgg1gbpzlZwRr2F4493",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"辣椒\"}"
            }
          },
          {
            "index": 1,
            "id": "call_01_OzvTW8ZbE8OqRfhhLdtd0846",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"猪肉\"}"
            }
          },
          {
            "index": 2,
            "id": "call_02_b1ph42v7UaaYrS7viALZ9156",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"大蒜\"}"
            }
          },
          {
            "index": 3,
            "id": "call_03_2g9c5erWv8Xb4i2iSAID4181",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"生姜\"}"
            }
          },
          {
            "index": 4,
            "id": "call_04_G7Jgu5iwBnXHKWPGdSrl1583",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"食用油\"}"
            }
          },
          {
            "index": 5,
            "id": "call_05_E44OPjxzRtp5Qajc2hMx5147",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"盐\"}"
            }
          },
          {
            "index": 6,
            "id": "call_06_fYqugLEKRCb2rVeJub0J6012",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"生抽\"}"
            }
          },
          {
            "index": 7,
            "id": "call_07_BPtz1T3SR5ocmjqewLab0511",
            "type": "function",
            "function": {
              "name": "xiao_wang",
              "arguments": "{\"物品\": \"淀粉\"}"
            }
          }
        ]
      },
      "logprobs": null,
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {
    "prompt_tokens": 435,
    "completion_tokens": 593,
    "total_tokens": 1028,
    "prompt_tokens_details": {
      "cached_tokens": 0
    },
    "completion_tokens_details": {
      "reasoning_tokens": 325
    },
    "prompt_cache_hit_tokens": 0,
    "prompt_cache_miss_tokens": 435
  },
  "system_fingerprint": "e72d3c7f663d3e1383b00b3884cb4501"
}

然后,我们假装在现实世界里,小王把菜买齐了,所以我们会在上次请求的基础上,构造出如下请求:

 {
   "model": "deepseek-flash",
   "messages": [
     {
       "role": "user",
       "content": "我想吃辣椒炒肉"
     },
+    {
+      "role": "assistant",
+      "content": "我这就准备食材:辣椒、猪肉和常用配料,再用防粘技巧来炒。",
+      "reasoning_content": "We need respond in Chinese. Need accomplish: buy ingredients via xiao_wang, then ask xiao_li cook with extra technique to avoid sticking. Need know \"额外技巧\" required. The description says 小李 needs extra technique otherwise dish fails. We need probably buy ingredients including 辣椒, 猪肉, etc, then call cook with technique. Could do parallel? Buying and cooking dependent—need ingredients first. But can buy all at once. Tool only one item per call. We can parallel multiple xiao_wang calls for ingredients, then xiao_li call after. Need infer common ingredients: 猪肉、辣椒、葱姜蒜、油盐酱醋 maybe. Could buy \"辣椒炒肉的食材\" but item name maybe accepted? Better buy pork and peppers, plus seasonings. They asked \"想吃辣椒炒肉\". We can buy 猪肉, 辣椒, 蒜, 姜, 葱, 生抽, 老抽? 淀粉? 油盐酱醋. Overkill. Need perhaps ingredient list: 五花肉/前腿肉、青椒、蒜、姜、豆豉? The task expects use xiao_wang to purchase ingredients and xiao_li cook. Since \"额外技巧\" important: 热锅凉油? For 辣椒炒肉 not sticking: \"先把锅烧热,用油滑锅,再下肉\" or \"热锅凉油、先煸辣椒再炒肉\". Need include. Let's call buy items in parallel. Then cook. Need maybe no need ask preference. Let's do.",
+      "tool_calls": [
+        {
+          "index": 0,
+          "id": "call_00_sADDPPgg1gbpzlZwRr2F4493",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"辣椒\"}"
+          }
+        },
+        {
+          "index": 1,
+          "id": "call_01_OzvTW8ZbE8OqRfhhLdtd0846",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"猪肉\"}"
+          }
+        },
+        {
+          "index": 2,
+          "id": "call_02_b1ph42v7UaaYrS7viALZ9156",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"大蒜\"}"
+          }
+        },
+        {
+          "index": 3,
+          "id": "call_03_2g9c5erWv8Xb4i2iSAID4181",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"生姜\"}"
+          }
+        },
+        {
+          "index": 4,
+          "id": "call_04_G7Jgu5iwBnXHKWPGdSrl1583",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"食用油\"}"
+          }
+        },
+        {
+          "index": 5,
+          "id": "call_05_E44OPjxzRtp5Qajc2hMx5147",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"盐\"}"
+          }
+        },
+        {
+          "index": 6,
+          "id": "call_06_fYqugLEKRCb2rVeJub0J6012",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"生抽\"}"
+          }
+        },
+        {
+          "index": 7,
+          "id": "call_07_BPtz1T3SR5ocmjqewLab0511",
+          "type": "function",
+          "function": {
+            "name": "xiao_wang",
+            "arguments": "{\"物品\": \"淀粉\"}"
+          }
+        }
+      ]
+    },
+    {
+      "role": "tool",
+      "content": "辣椒已经购买成功",
+      "tool_call_id": "call_00_sADDPPgg1gbpzlZwRr2F4493"
+    },
+    {
+      "role": "tool",
+      "content": "猪肉已经购买成功",
+      "tool_call_id": "call_01_OzvTW8ZbE8OqRfhhLdtd0846"
+    },
+    {
+      "role": "tool",
+      "content": "大蒜已经购买成功",
+      "tool_call_id": "call_02_b1ph42v7UaaYrS7viALZ9156"
+    },
+    {
+      "role": "tool",
+      "content": "生姜已经购买成功",
+      "tool_call_id": "call_03_2g9c5erWv8Xb4i2iSAID4181"
+    },
+    {
+      "role": "tool",
+      "content": "食用油已经购买成功",
+      "tool_call_id": "call_04_G7Jgu5iwBnXHKWPGdSrl1583"
+    },
+    {
+      "role": "tool",
+      "content": "盐已经购买成功",
+      "tool_call_id": "call_05_E44OPjxzRtp5Qajc2hMx5147"
+    },
+    {
+      "role": "tool",
+      "content": "生抽已经购买成功",
+      "tool_call_id": "call_06_fYqugLEKRCb2rVeJub0J6012"
+    },
+    {
+      "role": "tool",
+      "content": "淀粉已经购买成功",
+      "tool_call_id": "call_07_BPtz1T3SR5ocmjqewLab0511"
+    }
   ],
   "tools": [
     {
       "type": "function",
       "function": {
         "name": "xiao_wang",
         "description": "小王可以去菜市场购买东西,包括肉蛋奶葱姜蒜油盐酱醋等常见食材与调料,以及各种烹饪工具与锅碗瓢盆",
         "parameters": {
           "type": "object",
           "properties": {
             "物品": {
               "type": "string",
               "description": "要购买的物品名称"
             }
           },
           "required": ["物品"]
         }
       }
     },
     {
       "type": "function",
       "function": {
         "name": "xiao_li",
         "description": "小李是个厨子,会炒菜,但炒辣椒炒肉时需要额外技巧才能不沾锅,否则辣椒炒肉会制作失败",
         "parameters": {
           "type": "object",
           "properties": {
             "菜名": {
               "type": "string",
               "description": "要炒的菜的名称"
             },
             "额外技巧": {
               "type": "string",
               "description": "做菜时的额外技巧"
             }
           },
           "required": ["菜名"]
         }
       }
     }
   ]
 }

发送这个请求,我们得到如下回应:

{
  "id": "6e4f760e-7e77-4721-8b0c-ee0c31be4aac",
  "object": "chat.completion",
  "created": 1792229143,
  "model": "deepseek-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "食材已经买齐。现在让小李师傅做,并提醒他用“热锅凉油、润锅后下肉”的防粘技巧。",
        "reasoning_content": "Now call xiao_li with 菜名 and 额外技巧. Need specify. \"热锅凉油,先用油润锅并烧至微冒烟后倒出热油,再加凉油下肉;肉片提前用少许淀粉和生抽抓匀,下锅后先定型再翻动,可防粘锅。\" That's extra technique. Call.",
        "tool_calls": [
          {
            "index": 0,
            "id": "call_00_SE6RNMv7KcwxLeFry1WV8631",
            "type": "function",
            "function": {
              "name": "xiao_li",
              "arguments": "{\"菜名\": \"辣椒炒肉\", \"额外技巧\": \"先把锅烧热,用食用油充分润锅至微微冒烟,倒出热油后再加凉油;肉片提前用少许淀粉和生抽抓匀,下锅后先摊开定型再翻动,避免粘锅。\"}"
            }
          }
        ]
      },
      "logprobs": null,
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {
    "prompt_tokens": 1128,
    "completion_tokens": 216,
    "total_tokens": 1344,
    "prompt_tokens_details": {
      "cached_tokens": 896
    },
    "completion_tokens_details": {
      "reasoning_tokens": 78
    },
    "prompt_cache_hit_tokens": 896,
    "prompt_cache_miss_tokens": 232
  },
  "system_fingerprint": "e72d3c7f663d3e1383b00b3884cb4501"
}

现在,我们再在上次请求的基础上,再添加上上次回应的信息,并假装小李已经炒完了菜,就可以构造出如下请求:

 {
   "model": "deepseek-flash",
   "messages": [
     {
       "role": "user",
       "content": "我想吃辣椒炒肉"
     },
     {
       "role": "assistant",
       "content": "我这就准备食材:辣椒、猪肉和常用配料,再用防粘技巧来炒。",
       "reasoning_content": "We need respond in Chinese. Need accomplish: buy ingredients via xiao_wang, then ask xiao_li cook with extra technique to avoid sticking. Need know \"额外技巧\" required. The description says 小李 needs extra technique otherwise dish fails. We need probably buy ingredients including 辣椒, 猪肉, etc, then call cook with technique. Could do parallel? Buying and cooking dependent—need ingredients first. But can buy all at once. Tool only one item per call. We can parallel multiple xiao_wang calls for ingredients, then xiao_li call after. Need infer common ingredients: 猪肉、辣椒、葱姜蒜、油盐酱醋 maybe. Could buy \"辣椒炒肉的食材\" but item name maybe accepted? Better buy pork and peppers, plus seasonings. They asked \"想吃辣椒炒肉\". We can buy 猪肉, 辣椒, 蒜, 姜, 葱, 生抽, 老抽? 淀粉? 油盐酱醋. Overkill. Need perhaps ingredient list: 五花肉/前腿肉、青椒、蒜、姜、豆豉? The task expects use xiao_wang to purchase ingredients and xiao_li cook. Since \"额外技巧\" important: 热锅凉油? For 辣椒炒肉 not sticking: \"先把锅烧热,用油滑锅,再下肉\" or \"热锅凉油、先煸辣椒再炒肉\". Need include. Let's call buy items in parallel. Then cook. Need maybe no need ask preference. Let's do.",
       "tool_calls": [
         {
           "index": 0,
           "id": "call_00_sADDPPgg1gbpzlZwRr2F4493",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"辣椒\"}"
           }
         },
         {
           "index": 1,
           "id": "call_01_OzvTW8ZbE8OqRfhhLdtd0846",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"猪肉\"}"
           }
         },
         {
           "index": 2,
           "id": "call_02_b1ph42v7UaaYrS7viALZ9156",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"大蒜\"}"
           }
         },
         {
           "index": 3,
           "id": "call_03_2g9c5erWv8Xb4i2iSAID4181",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"生姜\"}"
           }
         },
         {
           "index": 4,
           "id": "call_04_G7Jgu5iwBnXHKWPGdSrl1583",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"食用油\"}"
           }
         },
         {
           "index": 5,
           "id": "call_05_E44OPjxzRtp5Qajc2hMx5147",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"盐\"}"
           }
         },
         {
           "index": 6,
           "id": "call_06_fYqugLEKRCb2rVeJub0J6012",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"生抽\"}"
           }
         },
         {
           "index": 7,
           "id": "call_07_BPtz1T3SR5ocmjqewLab0511",
           "type": "function",
           "function": {
             "name": "xiao_wang",
             "arguments": "{\"物品\": \"淀粉\"}"
           }
         }
       ]
     },
     {
       "role": "tool",
       "content": "辣椒已经购买成功",
       "tool_call_id": "call_00_sADDPPgg1gbpzlZwRr2F4493"
     },
     {
       "role": "tool",
       "content": "猪肉已经购买成功",
       "tool_call_id": "call_01_OzvTW8ZbE8OqRfhhLdtd0846"
     },
     {
       "role": "tool",
       "content": "大蒜已经购买成功",
       "tool_call_id": "call_02_b1ph42v7UaaYrS7viALZ9156"
     },
     {
       "role": "tool",
       "content": "生姜已经购买成功",
       "tool_call_id": "call_03_2g9c5erWv8Xb4i2iSAID4181"
     },
     {
       "role": "tool",
       "content": "食用油已经购买成功",
       "tool_call_id": "call_04_G7Jgu5iwBnXHKWPGdSrl1583"
     },
     {
       "role": "tool",
       "content": "盐已经购买成功",
       "tool_call_id": "call_05_E44OPjxzRtp5Qajc2hMx5147"
     },
     {
       "role": "tool",
       "content": "生抽已经购买成功",
       "tool_call_id": "call_06_fYqugLEKRCb2rVeJub0J6012"
     },
     {
       "role": "tool",
       "content": "淀粉已经购买成功",
       "tool_call_id": "call_07_BPtz1T3SR5ocmjqewLab0511"
     },
+    {
+      "role": "assistant",
+      "content": "食材已经买齐。现在让小李师傅做,并提醒他用“热锅凉油、润锅后下肉”的防粘技巧。",
+      "reasoning_content": "Now call xiao_li with 菜名 and 额外技巧. Need specify. \"热锅凉油,先用油润锅并烧至微冒烟后倒出热油,再加凉油下肉;肉片提前用少许淀粉和生抽抓匀,下锅后先定型再翻动,可防粘锅。\" That's extra technique. Call.",
+      "tool_calls": [
+        {
+          "index": 0,
+          "id": "call_00_SE6RNMv7KcwxLeFry1WV8631",
+          "type": "function",
+          "function": {
+            "name": "xiao_li",
+            "arguments": "{\"菜名\": \"辣椒炒肉\", \"额外技巧\": \"先把锅烧热,用食用油充分润锅至微微冒烟,倒出热油后再加凉油;肉片提前用少许淀粉和生抽抓匀,下锅后先摊开定型再翻动,避免粘锅。\"}"
+          }
+        }
+      ]
+    },
+    {
+      "role": "tool",
+      "content": "辣椒炒肉已经制作成功,没有粘锅,非常完美",
+      "tool_call_id": "call_00_SE6RNMv7KcwxLeFry1WV8631"
+    }
   ],
   "tools": [
     {
       "type": "function",
       "function": {
         "name": "xiao_wang",
         "description": "小王可以去菜市场购买东西,包括肉蛋奶葱姜蒜油盐酱醋等常见食材与调料,以及各种烹饪工具与锅碗瓢盆",
         "parameters": {
           "type": "object",
           "properties": {
             "物品": {
               "type": "string",
               "description": "要购买的物品名称"
             }
           },
           "required": ["物品"]
         }
       }
     },
     {
       "type": "function",
       "function": {
         "name": "xiao_li",
         "description": "小李是个厨子,会炒菜,但炒辣椒炒肉时需要额外技巧才能不沾锅,否则辣椒炒肉会制作失败",
         "parameters": {
           "type": "object",
           "properties": {
             "菜名": {
               "type": "string",
               "description": "要炒的菜的名称"
             },
             "额外技巧": {
               "type": "string",
               "description": "做菜时的额外技巧"
             }
           },
           "required": ["菜名"]
         }
       }
     }
   ]
 }

最后一次,API给我们如下返回。并且这一次回应的消息中,finish_reason的值为stop,也说明,LLM知道,这个“项目”完工了。

{
  "id": "9b85c879-6dd5-43b9-8964-8016fd5298d9",
  "object": "chat.completion",
  "created": 1792229477,
  "model": "deepseek-flash",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "辣椒炒肉已经做好啦,而且用了防粘技巧,没有粘锅,很成功!\n\n这道菜用了辣椒、猪肉、姜蒜,还有生抽和盐调味,肉片嫩滑、辣椒香辣,正好可以开吃。",
        "reasoning_content": "Need final Chinese, maybe describe done."
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 1369,
    "completion_tokens": 59,
    "total_tokens": 1428,
    "prompt_tokens_details": {
      "cached_tokens": 1152
    },
    "completion_tokens_details": {
      "reasoning_tokens": 8
    },
    "prompt_cache_hit_tokens": 1152,
    "prompt_cache_miss_tokens": 217
  },
  "system_fingerprint": "e72d3c7f663d3e1383b00b3884cb4501"
}

通过这个冗长的例子,你现在应该理解了LLM API的工作模式了,并且心里大致也隐隐对“Agent开发是做什么”有了一点点sense。我希望你此时最好自己把这个例子复刻一下,然后仔细思考下面的几个开放式问题:

  1. LLM API在整个“项目”过程中,到底做了什么?
  2. 员工/tool/function在整个“项目”过程中,又做了什么?
  3. 整个“项目”过程中,除了“员工”和“LLM”,还有个什么玩意?

想一想这三个浅显又深邃的问题,想明白了,相信你对"Agent"到底是什么东西,会有非常深刻的理解。并且对“Agent”能做哪些事情,也有了自己的理解。