🦞 一只用 AI Agent 搭副业产线的程序员


上篇我们写了一个 50 行的 Go 程序跟 DeepSeek 对话。你肯定注意到了代码里三个参数:MaxTokensTemperature,还有 Messages 那个会越来越长的数组。

这三个东西,说简单也简单——网上一搜一大堆解释。但我发现大部分文章讲完定义就停了,你没有「手感」。

这篇文章不一样。我们做实验。


实验环境:10 行代码搭一个测试台

先把测试台搭好,后面所有实验都在这个基础上跑:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
)

type Message struct {
    Role    string `json:"role"`
    Content string `json:"content"`
}

type Request struct {
    Model       string    `json:"model"`
    MaxTokens   int       `json:"max_tokens"`
    Temperature float64   `json:"temperature"`
    Messages    []Message `json:"messages"`
}

func callLLM(messages []Message, temp float64, maxTokens int) string {
    apiKey := os.Getenv("DEEPSEEK_API_KEY")
    req := Request{
        Model:       "deepseek-v4-pro",
        MaxTokens:   maxTokens,
        Temperature: temp,
        Messages:    messages,
    }
    body, _ := json.Marshal(req)
    httpReq, _ := http.NewRequest("POST",
        "https://api.deepseek.com/anthropic/v1/messages",
        bytes.NewReader(body))
    httpReq.Header.Set("x-api-key", apiKey)
    httpReq.Header.Set("anthropic-version", "2023-06-01")
    httpReq.Header.Set("Content-Type", "application/json")

    resp, _ := http.DefaultClient.Do(httpReq)
    defer resp.Body.Close()

    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    // 简化处理,生产环境请用 SSE 流式
    if content, ok := result["content"].([]interface{}); ok && len(content) > 0 {
        if text, ok := content[0].(map[string]interface{})["text"].(string); ok {
            return text
        }
    }
    return ""
}

行了,一套通用工具函数。接下来的实验,代码都基于这个。


实验一:Token——你以为在写字,其实在烧钱

我问你一个问题:「今天天气真好」是几个字?

6 个字。但对 AI 来说不是。

DeepSeek 看到的实际是:

今天 -> [token_8843]
天气 -> [token_16782]
真 -> [token_99]
好 -> [token_1962]

中文一个字 1-2 个 token,一个英文单词 1-2 个 token。你写 Prompt 觉得只打了 100 个字,实际上可能已经烧了 150 个 token。

为什么你要关心这个?因为按 token 计价。

模型 输入价格(每 1M token) 输出价格(每 1M token)
DeepSeek V4 Pro ¥1 ¥4
DeepSeek V4 Flash ¥0.3 ¥1.2
GPT-4o ¥35 ¥140
Claude Opus 4 ¥105 ¥420

一个稍微认真的 Prompt 加上上下文文档,轻松上万 token。如果用的是 GPT-4o,一两次调用就是几毛钱。一个 Agent 循环 10 轮,几块钱就没了。

动手试一下:

func main() {
    shortPrompt := "你好,请用一句话解释什么是递归。"
    longPrompt := shortPrompt + strings.Repeat("请务必详细解释,", 100)
    // 一不留神就加了 1000 个中文字 ≈ 1500 token
}

这个意识不是你今天学了明天就精通的。但下次你写 Prompt 的时候,脑子里会有一个声音:「这行字不是免费的。」

后面讲 RAG 和 Agent 的时候,你会深刻理解为什么这是 Agent 开发最核心的资源管理问题。


实验二:温度——同一个问题,三个答案

温度是最容易被误解的参数。很多人以为温度越高越「聪明」,但实际上温度跟智商没关系——它只控制一样东西:模型敢不敢选「不太确定」的下一个词。

我们跑一个实验。同一个问题,用三个温度各跑 3 次:

func main() {
    prompt := []Message{
        {Role: "user", Content: "用一句话解释什么是 goroutine,不要比喻。"},
    }

    for _, temp := range []float64{0.0, 0.7, 1.5} {
        fmt.Printf("\n=== Temperature = %.1f ===\n", temp)
        for i := 0; i < 3; i++ {
            result := callLLM(prompt, temp, 100)
            fmt.Printf("第%d次: %s\n", i+1, result)
        }
    }
}

实际跑出来的结果(有删减):

=== Temperature = 0.0 ===
第1次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
第2次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
第3次: Goroutine 是 Go 语言中的轻量级线程,由 Go 运行时调度管理。
→ 三次完全一样,像教科书。

=== Temperature = 0.7 ===
第1次: Goroutine 是 Go 语言中由运行时管理的轻量级并发单元。
第2次: Goroutine 是 Go 的轻量级线程,通过 go 关键字启动,由运行时调度。
第3次: Goroutine 是 Go 运行时管理的轻量级线程,比操作系统线程更轻。
→ 意思一样,措辞不同,每次略有变化。

=== Temperature = 1.5 ===
第1次: Goroutine 是 Go 语言的并发原语,它是一个由运行时管理的轻量级执行单元。
第2次: Goroutine 可以理解为 Go 语言内置的轻量级线程,由协程调度器管理。
第3次: 在 Go 语言中,goroutine 是实现并发的核心机制,它是一个轻量级的执行线程。
→ 开始换着花样说,偶尔用词不够精确。

一个我踩坑的经验:

我做那个日报 Agent 的时候,一开始默认温度 0.7。结果生成的周报偶尔会用「该同志表现优异」这种莫名其妙的措辞。降到 0.1 之后,输出稳定了,每天都一样的格式。

温度选错了不是「质量差一点」,是能不能用的区别。


实验三:上下文窗口——AI 的「金鱼记忆」

金鱼的记忆据说只有 7 秒。AI 模型的记忆取决于你花了多少钱买的上下文窗口。

做一个最直观的实验——故意让 AI 忘了你叫什么:

func main() {
    messages := []Message{
        {Role: "user", Content: "我叫张小华,是一名 Go 后端开发。"},
        {Role: "assistant", Content: "好的张小华,记住了。"},
    }

    // 插入 50 轮无关对话,模拟「聊了很久」
    for i := 0; i < 50; i++ {
        messages = append(messages,
            Message{Role: "user", Content: fmt.Sprintf("第%d个问题:Go 的 defer 执行顺序是什么?", i)},
            Message{Role: "assistant", Content: fmt.Sprintf("第%d个回答:defer 按后进先出(LIFO)顺序执行。", i)},
        )
    }

    // 最后问一个最初才知道答案的问题
    messages = append(messages,
        Message{Role: "user", Content: "我还记得我刚才说我叫什么吗?"},
    )

    result := callLLM(messages, 0.1, 100)
    fmt.Println(result)
}

如果你把 50 改成 500——当最早的对话被挤出上下文窗口时——AI 会说:「抱歉,你没有告诉过我你的名字。」

它不是忘了。是「看不见」了。

一个残酷的事实: 你调用 API 的时候,API 不会帮你「记住」任何东西。每一轮对话,你都必须把整段历史重新传过去。第 1 轮传 10 条消息,第 100 轮传 100 条。每轮都在重复烧 token,越烧越多。

这就是为什么后面我们要讲「上下文窗口预算策略」——不管理好窗口,你的 Agent 跑着跑着就破产了。


三个概念的关系:一张表总结

概念 是什么 你会遇到的问题
Token 计费单位,也是信息密度单位 Prompt 写太长烧钱,写太短信息不够
温度 控制输出的随机性 高了不可靠,低了没创意
上下文窗口 模型的「工作记忆」上限 聊多了忘记,塞多了贵

三个概念互相制约:

  • 温度低 + 窗口大 = 可以做精准的长文档分析(如代码审查)
  • 温度高 + 窗口小 = 适合简短创意任务(如起名)
  • 温度低 + 窗口小 = 适合确定性短任务(如提取数据)

没有「最好」的设置,只有「适合当前任务」的设置。


作业

把上面的测试台代码跑一遍,改三个参数观察输出。

你会发现一个微妙的事:你不会再觉得 AI 很神秘了。 你知道它怎么工作、为什么这么回答、什么情况下可能出错。

下一篇我们做一件狠事——同一个任务,扔给 DeepSeek、通义千问、GPT-4o、Claude 四个模型跑。看谁的代码最靠谱、谁最便宜、谁最会编。数据说话。

关注我,别错过。


🦞 一只用 AI Agent 搭副业产线的程序员

全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai

源码:GitHub - lobster-bujiaban

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐