第02篇-Go-Agent开发环境搭建与LLM接入
·
【AI Agent 与 Super Agent 构建实战】第 02 篇:Go Agent 开发环境搭建与 LLM 接入
本系列定位:用 Go 语言从零构建各类 AI Agent,覆盖 ReAct、Plan-and-Execute、Multi-Agent、Super Agent 等核心设计模式的完整工程实现。
本篇你将学到
- 搭建 Go Agent 开发环境(go-openai + Ollama)
- 实现多 Provider 统一抽象层(OpenAI / Anthropic / Ollama 一套接口)
- 流式输出(Streaming)的完整实现
- AgentForge 项目骨架搭建与配置驱动架构
学完本篇,你将拥有一个能接入任意 LLM 的 Go 项目骨架,后续所有 Agent 功能都在此基础上扩展。
一、技术栈与环境准备
1.1 整体架构
1.2 环境要求
| 组件 | 版本要求 | 说明 |
|---|---|---|
| Go | 1.23+ | 需要 generics 支持 |
| go-openai | latest | OpenAI API Go SDK,兼容所有 OpenAI 格式端点 |
| Ollama | 0.5+ | 本地推理引擎(可选,用于离线开发) |
| 操作系统 | Linux / macOS / Windows | 跨平台支持 |
1.3 项目初始化
mkdir agentforge
cd agentforge
go mod init github.com/yourname/agentforge
# 核心依赖
go get github.com/sashabaranov/go-openai
go get github.com/spf13/viper # 配置管理
go get github.com/spf13/cobra # CLI 框架
项目目录结构:
agentforge/
├── cmd/
│ └── root.go # CLI 入口
├── internal/
│ ├── config/ # 配置管理
│ ├── provider/ # LLM Provider 抽象层
│ ├── session/ # 会话管理
│ └── agent/ # Agent 核心逻辑(后续扩展)
├── config.yaml # 配置文件
├── go.mod
└── main.go
二、配置驱动架构
2.1 配置文件设计
# config.yaml
provider:
# 默认使用的 Provider
default: openai
# OpenAI 配置(也兼容第三方 OpenAI 格式 API)
openai:
api_key: "sk-xxxxxxxx"
base_url: "https://api.openai.com/v1"
model: "gpt-4o"
max_tokens: 4096
temperature: 0.7
# Ollama 本地配置
ollama:
base_url: "http://localhost:11434/v1"
api_key: "ollama" # Ollama 不需要真实 key
model: "qwen2.5:14b"
max_tokens: 4096
temperature: 0.7
# Anthropic 配置
anthropic:
api_key: "sk-ant-xxxxxxxx"
base_url: "https://api.anthropic.com/v1"
model: "claude-3-5-sonnet"
max_tokens: 4096
temperature: 0.7
session:
system_prompt: "你是一个有帮助的 AI 助手。"
max_history: 20 # 保留最近 20 条消息
2.2 配置加载代码
// internal/config/config.go
package config
import (
"fmt"
"os"
"github.com/spf13/viper"
)
// AppConfig 应用全局配置
type AppConfig struct {
Provider ProviderConfig `mapstructure:"provider"`
Session SessionConfig `mapstructure:"session"`
}
// ProviderConfig 包含所有 Provider 的配置
type ProviderConfig struct {
Default string `mapstructure:"default"` // openai / ollama / anthropic
OpenAI ProviderParams `mapstructure:"openai"`
Ollama ProviderParams `mapstructure:"ollama"`
Anthropic ProviderParams `mapstructure:"anthropic"`
}
// ProviderParams 单个 Provider 的连接参数
type ProviderParams struct {
APIKey string `mapstructure:"api_key"`
BaseURL string `mapstructure:"base_url"`
Model string `mapstructure:"model"`
MaxTokens int `mapstructure:"max_tokens"`
Temperature float32 `mapstructure:"temperature"`
}
// SessionConfig 会话配置
type SessionConfig struct {
SystemPrompt string `mapstructure:"system_prompt"`
MaxHistory int `mapstructure:"max_history"`
}
// Load 加载配置文件
func Load(path string) (*AppConfig, error) {
viper.SetConfigFile(path)
viper.AutomaticEnv() // 环境变量覆盖
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("读取配置失败: %w", err)
}
var cfg AppConfig
if err := viper.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("解析配置失败: %w", err)
}
// 环境变量优先:API_KEY 可通过环境变量注入(生产安全)
if key := os.Getenv("OPENAI_API_KEY"); key != "" {
cfg.Provider.OpenAI.APIKey = key
}
return &cfg, nil
}
// GetProviderParams 根据名称获取 Provider 参数
func (c *AppConfig) GetProviderParams(name string) (ProviderParams, error) {
switch name {
case "openai":
return c.Provider.OpenAI, nil
case "ollama":
return c.Provider.Ollama, nil
case "anthropic":
return c.Provider.Anthropic, nil
default:
return ProviderParams{}, fmt.Errorf("未知 provider: %s", name)
}
}
设计要点:API Key 支持环境变量注入,避免硬编码在配置文件中。生产环境应使用密钥管理服务。
三、Provider 抽象层设计
这是本篇的核心——一个统一的 LLM 接口,屏蔽不同 Provider 的差异。
3.1 抽象接口定义
// internal/provider/provider.go
package provider
import (
"context"
"github.com/yourname/agentforge/internal/config"
)
// Message 统一消息格式
type Message struct {
Role string `json:"role"` // system / user / assistant / tool
Content string `json:"content"` // 消息内容
}
// ChatRequest 聊天请求
type ChatRequest struct {
Messages []Message
Model string
MaxTokens int
Temperature float32
}
// ChatResponse 聊天响应
type ChatResponse struct {
Content string // 回复文本
TokensUsed int // 消耗的 Token 数
Model string // 实际使用的模型
}
// LLMProvider LLM 提供者抽象接口
type LLMProvider interface {
// Chat 同步聊天
Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error)
// StreamChat 流式聊天,通过 channel 逐块返回
StreamChat(ctx context.Context, req ChatRequest) (<-chan string, error)
}
// New 根据配置创建 Provider 实例(工厂模式)
func New(name string, cfg *config.AppConfig) (LLMProvider, error) {
params, err := cfg.GetProviderParams(name)
if err != nil {
return nil, err
}
switch name {
case "openai", "ollama":
// Ollama 兼容 OpenAI API 格式,共用同一个实现
return NewOpenAICompatible(params), nil
case "anthropic":
return NewAnthropicProvider(params), nil
default:
return nil, fmt.Errorf("不支持的 provider: %s", name)
}
}
3.2 OpenAI 兼容实现(同时支持 Ollama)
由于 Ollama 提供了 OpenAI 兼容端点,一个实现即可同时支持 OpenAI 和 Ollama:
// internal/provider/openai.go
package provider
import (
"context"
"fmt"
"github.com/sashabaranov/go-openai"
"github.com/yourname/agentforge/internal/config"
)
// OpenAICompatible 兼容 OpenAI API 格式的 Provider
type OpenAICompatible struct {
client *openai.Client
config config.ProviderParams
}
// NewOpenAICompatible 创建 OpenAI 兼容 Provider
func NewOpenAICompatible(cfg config.ProviderParams) *OpenAICompatible {
// 自定义 BaseURL(用于 Ollama 或第三方 API)
clientConfig := openai.DefaultConfig(cfg.APIKey)
if cfg.BaseURL != "" {
clientConfig.BaseURL = cfg.BaseURL
}
return &OpenAICompatible{
client: openai.NewClientWithConfig(clientConfig),
config: cfg,
}
}
// Chat 同步聊天
func (p *OpenAICompatible) Chat(ctx context.Context, req ChatRequest) (*ChatResponse, error) {
// 转换消息格式
var msgs []openai.ChatCompletionMessage
for _, m := range req.Messages {
msgs = append(msgs, openai.ChatCompletionMessage{
Role: m.Role,
Content: m.Content,
})
}
// 构建请求
model := req.Model
if model == "" {
model = p.config.Model
}
maxTokens := req.MaxTokens
if maxTokens == 0 {
maxTokens = p.config.MaxTokens
}
temp := req.Temperature
if temp == 0 {
temp = p.config.Temperature
}
resp, err := p.client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: model,
Messages: msgs,
MaxTokens: maxTokens,
Temperature: temp,
})
if err != nil {
return nil, fmt.Errorf("LLM 调用失败: %w", err)
}
return &ChatResponse{
Content: resp.Choices[0].Message.Content,
TokensUsed: resp.Usage.TotalTokens,
Model: resp.Model,
}, nil
}
// StreamChat 流式聊天
func (p *OpenAICompatible) StreamChat(ctx context.Context, req ChatRequest) (<-chan string, error) {
var msgs []openai.ChatCompletionMessage
for _, m := range req.Messages {
msgs = append(msgs, openai.ChatCompletionMessage{
Role: m.Role,
Content: m.Content,
})
}
model := req.Model
if model == "" {
model = p.config.Model
}
stream, err := p.client.CreateChatCompletionStream(ctx, openai.ChatCompletionRequest{
Model: model,
Messages: msgs,
MaxTokens: p.config.MaxTokens,
Temperature: p.config.Temperature,
})
if err != nil {
return nil, fmt.Errorf("创建流式请求失败: %w", err)
}
ch := make(chan string, 100)
go func() {
defer close(ch)
defer stream.Close()
for {
resp, err := stream.Recv()
if err != nil {
if err.Error() == "EOF" {
return
}
ch <- fmt.Sprintf("\n[错误] %v", err)
return
}
if len(resp.Choices) > 0 {
delta := resp.Choices[0].Delta.Content
if delta != "" {
ch <- delta
}
}
}
}()
return ch, nil
}
3.3 流式输出效果演示
// 流式输出的使用方式
func main() {
provider, _ := provider.New("ollama", cfg)
ch, _ := provider.StreamChat(ctx, provider.ChatRequest{
Messages: []provider.Message{
{Role: "user", Content: "用三句话解释什么是 AI Agent"},
},
})
// 逐块打印,实现打字机效果
for chunk := range ch {
fmt.Print(chunk)
}
}
运行效果(文字逐块出现):
AI Agent 是一个能够自主感知环境、做出决策并采取行动来达成目标的人工智能系统。
它由大语言模型(LLM)作为大脑,通过调用外部工具来执行实际操作。
与普通聊天机器人不同,Agent 具备自主循环能力,能多步推理和行动直到任务完成。
四、会话管理
4.1 Session 设计
// internal/session/session.go
package session
import (
"context"
"fmt"
"github.com/yourname/agentforge/internal/provider"
)
// Session 管理一次对话的完整上下文
type Session struct {
provider provider.LLMProvider
systemPrompt string
maxHistory int
messages []provider.Message
}
// New 创建新会话
func New(p provider.LLMProvider, systemPrompt string, maxHistory int) *Session {
s := &Session{
provider: p,
systemPrompt: systemPrompt,
maxHistory: maxHistory,
messages: []provider.Message{},
}
// System Prompt 作为第一条消息
if systemPrompt != "" {
s.messages = append(s.messages, provider.Message{
Role: "system",
Content: systemPrompt,
})
}
return s
}
// Chat 发送消息并获取回复
func (s *Session) Chat(ctx context.Context, userInput string) (string, error) {
// 追加用户消息
s.messages = append(s.messages, provider.Message{
Role: "user",
Content: userInput,
})
// 调用 LLM
resp, err := s.provider.Chat(ctx, provider.ChatRequest{
Messages: s.messages,
})
if err != nil {
return "", err
}
// 追加助手回复
s.messages = append(s.messages, provider.Message{
Role: "assistant",
Content: resp.Content,
})
// 历史消息超出上限时截断(保留 system prompt)
s.truncate()
return resp.Content, nil
}
// StreamChat 流式发送消息
func (s *Session) StreamChat(ctx context.Context, userInput string) (<-chan string, error) {
s.messages = append(s.messages, provider.Message{
Role: "user",
Content: userInput,
})
ch, err := s.provider.StreamChat(ctx, provider.ChatRequest{
Messages: s.messages,
})
if err != nil {
return nil, err
}
// 包装 channel:流结束后收集完整回复存入历史
wrappedCh := make(chan string, 100)
go func() {
defer close(wrappedCh)
var fullContent string
for chunk := range ch {
fullContent += chunk
wrappedCh <- chunk
}
s.messages = append(s.messages, provider.Message{
Role: "assistant",
Content: fullContent,
})
s.truncate()
}()
return wrappedCh, nil
}
// truncate 截断历史消息(保留 system prompt + 最近 N 条)
func (s *Session) truncate() {
// 保留第一条 system prompt + 最近 maxHistory 条
if len(s.messages) <= s.maxHistory+1 {
return
}
systemMsgs := []provider.Message{}
otherMsgs := []provider.Message{}
for _, m := range s.messages {
if m.Role == "system" {
systemMsgs = append(systemMsgs, m)
} else {
otherMsgs = append(otherMsgs, m)
}
}
// 截断非 system 消息
start := len(otherMsgs) - s.maxHistory
if start < 0 {
start = 0
}
s.messages = append(systemMsgs, otherMsgs[start:]...)
}
// History 获取当前对话历史
func (s *Session) History() []provider.Message {
return s.messages
}
五、CLI 入口整合
5.1 完整的 main.go
// main.go
package main
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/yourname/agentforge/internal/config"
"github.com/yourname/agentforge/internal/provider"
"github.com/yourname/agentforge/internal/session"
)
func main() {
var configFile string
rootCmd := &cobra.Command{
Use: "agentforge",
Short: "AgentForge - AI Agent 锻造平台",
Long: "从零构建的 AI Agent 框架,本篇是 v0.1 基础对话引擎",
Run: func(cmd *cobra.Command, args []string) {
runChat(configFile)
},
}
rootCmd.Flags().StringVarP(&configFile, "config", "c", "config.yaml", "配置文件路径")
rootCmd.Execute()
}
func runChat(configFile string) {
// 加载配置
cfg, err := config.Load(configFile)
if err != nil {
fmt.Printf("配置加载失败: %v\n", err)
os.Exit(1)
}
// 创建 Provider
providerName := cfg.Provider.Default
p, err := provider.New(providerName, cfg)
if err != nil {
fmt.Printf("Provider 创建失败: %v\n", err)
os.Exit(1)
}
// 创建会话
sess := session.New(p, cfg.Session.SystemPrompt, cfg.Session.MaxHistory)
fmt.Printf("🤖 AgentForge v0.1 已启动 (Provider: %s)\n", providerName)
fmt.Println("输入消息开始对话,输入 /quit 退出")
fmt.Println(strings.Repeat("-", 50))
scanner := bufio.NewScanner(os.Stdin)
ctx := context.Background()
for {
fmt.Print("\n你 > ")
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
if input == "/quit" {
break
}
if input == "" {
continue
}
if input == "/history" {
for _, m := range sess.History() {
fmt.Printf(" [%s] %s\n", m.Role, truncate(m.Content, 60))
}
continue
}
// 流式输出
fmt.Print("AI > ")
ch, err := sess.StreamChat(ctx, input)
if err != nil {
fmt.Printf("错误: %v\n", err)
continue
}
for chunk := range ch {
fmt.Print(chunk)
}
fmt.Println()
}
fmt.Println("再见!")
}
func truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
}
5.2 运行效果
$ go run main.go
🤖 AgentForge v0.1 已启动 (Provider: ollama)
输入消息开始对话,输入 /quit 退出
--------------------------------------------------
你 > 我叫张三,记住我的名字
AI > 你好张三!很高兴认识你,我已经记住了你的名字。
你 > 我叫什么?
AI > 你叫张三。
你 > /history
[system] 你是一个有帮助的 AI 助手。
[user] 我叫张三,记住我的名字
[assistant] 你好张三!很高兴认识你,我已经记住了你的名字。
[user] 我叫什么?
[assistant] 你叫张三。
本篇小结
| 知识点 | 核心内容 |
|---|---|
| Provider 抽象层 | 定义 LLMProvider 接口,屏蔽 OpenAI / Ollama / Anthropic 差异 |
| OpenAI 兼容策略 | Ollama 兼容 OpenAI API 格式,一个实现覆盖两个 Provider |
| 流式输出 | StreamChat() 通过 channel 逐块返回,实现打字机效果 |
| 会话管理 | Session 维护消息历史,支持截断和持久化 |
| 配置驱动 | YAML 配置 + 环境变量覆盖,API Key 不硬编码 |
| AgentForge v0.1 | 完整的 CLI 对话工具,支持多 Provider 切换 |
下篇预告
第 03 篇:LLM 核心机制回顾 — 提示工程与上下文窗口
深入理解 Prompt 结构化设计、Token 计费机制、Context Window 管理策略——这些是后续 Agent 设计的底层基础。
如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。
更多推荐
所有评论(0)