模块一总结:一个 Docker Compose 搭好你的 AI 开发环境
·
🦞 一只用 AI Agent 搭副业产线的程序员
模块一结束了。10 篇文章,从「AI 能帮程序员做什么」到「AI 的边界在哪」。
但你可能有个问题:每次写代码都要重新搭环境——注册 API Key、配置环境变量、装各种依赖。能不能一次性搭好?
这篇给你答案:一个 Docker Compose 文件,把 AI 开发需要的所有基础设施拉起来。
你需要什么
一个完整的 AI 开发环境包含这些组件:
| 组件 | 作用 | 你会用到的场景 |
|---|---|---|
| DeepSeek 代理 | 统一 API 入口,避免硬编码 Key | 所有调用 |
| Ollama | 本地模型运行环境 | 高频调用 / 隐私敏感 |
| Open WebUI | 本地版 ChatGPT 界面 | Prompt 调试 |
| Qdrant(向量数据库) | 存储和搜索 Embedding 向量 | 模块三 RAG |
| Redis | 缓存 API 响应,省 Token | Agent 开发 |
一键部署
# docker-compose.yml
version: "3.8"
services:
# 1. 本地模型引擎
ollama:
image: ollama/ollama:latest
container_name: ai-ollama
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
environment:
- OLLAMA_KEEP_ALIVE=24h
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
# 2. 本地 ChatGPT 界面(调试 Prompt 巨方便)
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: ai-webui
ports:
- "13000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- WEBUI_SECRET_KEY=change-me-to-random-string
volumes:
- webui_data:/app/backend/data
depends_on:
- ollama
restart: unless-stopped
# 3. 向量数据库(模块三用)
qdrant:
image: qdrant/qdrant:latest
container_name: ai-qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_data:/qdrant/storage
restart: unless-stopped
# 4. Redis(Token 缓存、Agent 状态)
redis:
image: redis:7-alpine
container_name: ai-redis
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
ollama_data:
webui_data:
qdrant_data:
redis_data:
# 启动全家桶(首次启动会拉取镜像,耐心等几分钟)
docker compose up -d
# 拉一个本地模型(可选,不需要本地模型可以跳过)
docker exec -it ai-ollama ollama pull qwen2.5-coder:7b
# 确认各组件状态
docker compose ps
启动完成后:
| 服务 | 地址 | 用途 |
|---|---|---|
| Ollama API | http://localhost:11434 | 本地模型 API |
| Open WebUI | http://localhost:13000 | 聊天调试界面 |
| Qdrant | http://localhost:6333 | 向量数据库 |
| Redis | localhost:6379 | 缓存 |
配置你的 Go 项目模板
有了基础设施,还需要一个趁手的 Go 项目模板。我整理了一个:
ai-project/
├── main.go
├── go.mod
├── .env.example
├── internal/
│ ├── llm/
│ │ └── client.go # LLM 调用客户端
│ ├── embed/
│ │ └── embeddings.go # Embedding 工具
│ ├── cache/
│ │ └── redis.go # Redis 缓存层
│ └── config/
│ └── config.go # 配置管理
└── scripts/
└── setup.sh # 初始化脚本
核心配置:
// internal/config/config.go
package config
import "os"
type Config struct {
// API 配置
DeepSeekAPIKey string
DeepSeekBaseURL string
// 本地服务
OllamaURL string
QdrantURL string
RedisAddr string
// 默认参数
DefaultModel string
DefaultTemp float64
DefaultMaxToken int
}
func Load() *Config {
return &Config{
DeepSeekAPIKey: getEnv("DEEPSEEK_API_KEY", ""),
DeepSeekBaseURL: getEnv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/anthropic"),
OllamaURL: getEnv("OLLAMA_URL", "http://localhost:11434"),
QdrantURL: getEnv("QDRANT_URL", "http://localhost:6334"),
RedisAddr: getEnv("REDIS_ADDR", "localhost:6379"),
DefaultModel: getEnv("DEFAULT_MODEL", "deepseek-v4-flash"),
DefaultTemp: getEnvFloat("DEFAULT_TEMP", 0.1),
DefaultMaxToken: getEnvInt("DEFAULT_MAX_TOKENS", 500),
}
}
func getEnv(key, defaultVal string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultVal
}
LLM 客户端(支持多后端切换):
// internal/llm/client.go
package llm
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type Client struct {
BaseURL string
APIKey string
Model string
HTTP *http.Client
}
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
func NewClient(baseURL, apiKey, model string) *Client {
return &Client{
BaseURL: baseURL,
APIKey: apiKey,
Model: model,
HTTP: &http.Client{},
}
}
func (c *Client) Chat(messages []Message, temp float64, maxTokens int) (string, error) {
body := map[string]interface{}{
"model": c.Model,
"max_tokens": maxTokens,
"temperature": temp,
"messages": messages,
}
jsonBody, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", c.BaseURL+"/v1/messages", bytes.NewReader(jsonBody))
req.Header.Set("x-api-key", c.APIKey)
req.Header.Set("anthropic-version", "2023-06-01")
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTP.Do(req)
if err != nil {
return "", fmt.Errorf("请求失败: %w", err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if content, ok := result["content"].([]interface{}); ok && len(content) > 0 {
if text, ok := content[0].(map[string]interface{})["text"].(string); ok {
return text, nil
}
}
return "", fmt.Errorf("无法解析响应")
}
// 快速切换后端
func NewDeepSeekClient(apiKey string) *Client {
return NewClient("https://api.deepseek.com/anthropic", apiKey, "deepseek-v4-flash")
}
func NewOllamaClient(model string) *Client {
return NewClient("http://localhost:11434/api", "", model)
}
验证环境:跑一个集成测试
package main
import (
"fmt"
"os"
"ai-project/internal/config"
"ai-project/internal/llm"
)
func main() {
cfg := config.Load()
client := llm.NewDeepSeekClient(cfg.DeepSeekAPIKey)
messages := []llm.Message{
{Role: "user", Content: "请验证环境是否正常,回复 'AI 开发环境已就绪'。"},
}
result, err := client.Chat(messages, 0.1, 100)
if err != nil {
fmt.Printf("环境验证失败: %v\n", err)
os.Exit(1)
}
fmt.Println(result)
fmt.Println("\n✅ 所有组件正常运行!")
}
go run main.go
# AI 开发环境已就绪。
# ✅ 所有组件正常运行!
模块一知识地图
| 篇 | 核心知识 | 你的武器 |
|---|---|---|
| 1 | 5 个真实可用场景 | 样板代码生成、注释、审查、日志分析、自动化 |
| 2 | LLM 工作原理 | 50 行 Go 对话程序 |
| 3 | Token/温度/上下文 | 三参数对照实验 |
| 4 | 主流模型横评 | 4 任务 × 5 模型的完整数据 |
| 5 | 第一个 LLM 程序 | 5 分钟从零到运行 |
| 6 | API 价格真相 | 100 次调用的真实账单 |
| 7 | 本地 vs 云端 | Ollama 部署 + 混合策略 |
| 8 | Embedding | 50 行语义相似度计算 |
| 9 | AI 的边界 | 6 个翻车案例 + 防御策略 |
| 10 | 开发环境 | Docker Compose 全家桶 + Go 模板 |
接下来 —— 模块二:Prompt Engineering 实战
你已经知道 AI 怎么调了。接下来 10 篇文章,我们要把 Prompt 当代码写:
- 不再把 Prompt 写在
.txt里复制粘贴 - 用 JSON 配置驱动 Prompt
- 变量替换、条件分支、模板继承
- 自动化测试——同一个 Prompt 跑 100 次,统计通过率
- 安全防御——Prompt 注入攻防
下一篇:Prompt 的本质不是「聊天」,是「编程」。用同一个任务的好 Prompt vs 烂 Prompt,让你直观看到差距。
关注我,别错过。
🦞 一只用 AI Agent 搭副业产线的程序员
全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai
更多推荐
所有评论(0)