Vikusha:用500行Go代码揭开AI智能体的神秘面纱——从Agent Loop到生产级框架的完整实战
引言:AI智能体真的那么神秘吗?
2026年,AI Agent已经成为技术圈最热门的话题。从DeerFlow到LangGraph,从AutoGPT到Claude Code,各种智能体框架层出不穷。但当你真正深入这些框架的源码,会发现一个惊人的事实:智能体的核心逻辑,不过是一个简单的循环。
这个发现来自一个名叫Vikusha的Go语言框架。它的作者在构建个人AI智能体nevinho时,意识到那些看似复杂的"推理引擎"、"决策系统",本质上都可以用一个不到500行的循环来描述。于是他决定把这个核心抽象出来,创建了一个极简框架——Vikusha。
本文将带你深入理解AI智能体的底层原理,从Agent Loop的概念模型,到Vikusha的架构设计,再到生产级代码实现。读完这篇文章,你会发现:智能体不再神秘,它只是模型决策、外部执行、模型再决策的迭代过程。
一、智能体的本质:一个简单的循环
1.1 从用户视角到系统视角
当你在Claude或ChatGPT中问"帮我分析这个项目的代码结构"时,你看到的是:
用户输入 → AI"思考" → 返回结果
但实际上,系统内部发生的是一系列迭代的交互:
第一轮:用户消息 → 模型 → 返回工具调用(read_file)
第二轮:工具结果 → 模型 → 返回工具调用(list_directory)
第三轮:工具结果 → 模型 → 返回工具调用(analyze_code)
...
第N轮:工具结果 → 模型 → 返回最终文本答案
这就是Agent Loop(智能体循环)的本质。
1.2 Agent Loop的形式化定义
用伪代码描述,Agent Loop的核心逻辑如下:
def agent_loop(messages, tools, model):
while True:
# 1. 调用模型
response = model.complete(messages, tools)
# 2. 检查是否有工具调用
if response.has_tool_use():
# 3. 执行工具
tool_results = execute_tools(response.tool_calls, tools)
# 4. 将工具结果追加到消息历史
messages.append(tool_results)
# 5. 继续循环
continue
else:
# 6. 无工具调用,返回最终答案
return response.text
这个循环揭示了智能体的三个核心要素:
- 模型(Model):负责决策,决定是直接回答还是调用工具
- 工具(Tools):负责执行,提供模型不具备的能力(文件读写、网络请求等)
- 消息历史(Messages):负责记忆,维护对话上下文
1.3 为什么这个抽象如此重要?
理解Agent Loop的重要性在于:
- 去神秘化:智能体不是某种神秘的"推理引擎",而是一个确定性的迭代过程
- 可预测性:每一轮循环都是可观测、可调试的,不存在"黑盒"
- 可扩展性:增加新能力只需添加新工具,核心循环不变
- 可替换性:切换模型、切换工具、切换传输层,都不影响核心逻辑
Vikusha的设计哲学就是:框架只负责这个循环,其他一切由用户定义。
二、Vikusha架构深度解析
2.1 设计理念:极简核心 + 用户定义
传统的AI框架往往采用"全家桶"设计:
LangChain: 模型抽象 + 提示词模板 + 向量存储 + 工具链 + 记忆管理 + ...
AutoGPT: 目标分解 + 任务规划 + 执行引擎 + 结果评估 + ...
这些框架试图覆盖所有场景,但代价是:
- 学习曲线陡峭
- 抽象层过多
- 调试困难
- 性能开销
Vikusha反其道而行之,采用"极简核心"设计:
Vikusha: Agent Loop(500行) + 用户自定义(一切)
框架只提供:
- 循环控制:管理消息流转、工具执行、退出判断
- 统一接口:屏蔽不同模型提供商的API差异
- 类型定义:通用的消息、工具、响应数据结构
其他一切——系统提示词、工具集、传输层、错误处理——全部交给用户。
2.2 核心架构图
┌─────────────────────────────────────────────────────────────┐
│ Vikusha Framework │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Agent Loop (Core) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Message │───▶│ Model │───▶│ Response │ │ │
│ │ │ History │ │ Complete │ │ Parser │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ ▲ │ │ │
│ │ │ ┌────────────┐ │ │ │
│ │ │ │ Tools │◀───────┘ │ │
│ │ └──────────────│ Executor │ │ │
│ │ └────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Provider Adapters (Pluggable) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Anthropic│ │ OpenAI │ │ OpenRouter│ ... │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
User Provides:
- System Prompt
- Tool Definitions
- Transport Layer (HTTP/SSE/WebSocket)
- Error Handling Strategy
2.3 核心类型定义
Vikusha定义了一组通用的内部表示形式,用于屏蔽不同提供商的差异:
// llm/block.go - 核心消息块类型
package llm
// Block 表示消息的一个组成部分
type Block struct {
Type BlockType // text, tool_use, tool_result
// text 类型的内容
Text string
// tool_use 类型的内容
ToolID string
ToolName string
ToolInput map[string]interface{}
// tool_result 类型的内容
ToolResultID string
IsError bool
}
type BlockType int
const (
BlockText BlockType = iota
BlockToolUse
BlockToolResult
)
// Message 表示一条完整的消息
type Message struct {
Role string // "user", "assistant", "system"
Content []Block // 消息内容(可包含多个块)
}
// Tool 定义工具接口
type Tool interface {
Name() string
Description() string
Schema() map[string]interface{} // JSON Schema
Run(input map[string]interface{}) (string, error)
}
// CompleteFunc 定义模型调用接口
type CompleteFunc func(ctx context.Context, messages []Message, tools []Tool) (*Response, error)
// Response 表示模型响应
type Response struct {
Content []Block
Stop bool // 是否应该停止循环
}
这个设计的精妙之处在于:
- Block是统一货币:无论Anthropic还是OpenAI,所有内容都转换为Block
- Tool是统一接口:工具只需实现4个方法,框架自动处理调用逻辑
- CompleteFunc是统一入口:不同提供商只需实现这个函数签名
三、Agent Loop的四个关键陷阱
在实现Agent Loop时,有四个极易导致错误的要点。这些陷阱在官方文档中往往一笔带过,但在实际开发中会耗费大量调试时间。
3.1 陷阱一:退出条件判断
错误做法:依赖API返回的stop_reason字段
// ❌ 错误:依赖stop_reason
if response.StopReason == "end_turn" {
return response.Text
}
问题:当达到max_tokens限制时,stop_reason会是"max_tokens",但响应中可能仍包含tool_use块。如果此时退出,工具调用将丢失。
正确做法:检查响应内容中是否包含tool_use块
// ✅ 正确:检查内容类型
func shouldContinue(response *Response) bool {
for _, block := range response.Content {
if block.Type == BlockToolUse {
return true
}
}
return false
}
// Agent Loop 中的使用
for {
response, _ := model.Complete(messages, tools)
if shouldContinue(response) {
// 有工具调用,继续循环
messages = append(messages, assistantMessage(response))
toolResults := executeTools(response, tools)
messages = append(messages, userMessage(toolResults))
} else {
// 无工具调用,返回最终答案
return extractText(response)
}
}
3.2 陷阱二:消息完整性
错误做法:将文本和工具调用拆分为两条消息
// ❌ 错误:拆分发送
if response.HasText() {
messages = append(messages, Message{
Role: "assistant",
Content: []Block{{Type: BlockText, Text: response.Text}},
})
}
if response.HasToolUse() {
messages = append(messages, Message{
Role: "assistant",
Content: response.ToolUseBlocks,
})
}
问题:当模型同时返回文本和工具调用时(例如:"我来帮你读取这个文件" + tool_use),如果拆分为两条消息,后续请求中的tool_use_id将无法匹配,导致API返回错误。
正确做法:将所有内容作为一条完整的assistant消息
// ✅ 正确:完整追加
func assistantMessage(response *Response) Message {
return Message{
Role: "assistant",
Content: response.Content, // 包含所有块(text + tool_use)
}
}
3.3 陷阱三:并行工具结果的聚合
错误做法:将多个工具结果分散发送
// ❌ 错误:分散发送
for _, result := range toolResults {
messages = append(messages, Message{
Role: "user",
Content: []Block{result},
})
}
问题:如果模型并行调用了3个工具(read_file_a, read_file_b, read_file_c),分散发送会导致工具调用与结果的配对关系被破坏。
正确做法:将所有结果封装在单个user消息中
// ✅ 正确:聚合发送
func userMessage(toolResults []Block) Message {
return Message{
Role: "user",
Content: toolResults, // 所有tool_result块在一条消息中
}
}
3.4 陷阱四:错误即数据
错误做法:工具执行失败时抛出异常,中断循环
// ❌ 错误:异常中断
func (t *ReadFileTool) Run(input map[string]interface{}) (string, error) {
content, err := os.ReadFile(input["path"].(string))
if err != nil {
return "", err // 抛出异常
}
return string(content), nil
}
问题:用户会收到一个无响应的错误,模型也无法尝试其他方案。
正确做法:将错误包装为tool_result反馈给模型
// ✅ 正确:错误作为数据
func executeTools(response *Response, tools map[string]Tool) []Block {
var results []Block
for _, block := range response.Content {
if block.Type == BlockToolUse {
tool := tools[block.ToolName]
output, err := tool.Run(block.ToolInput)
result := Block{
Type: BlockToolResult,
ToolResultID: block.ToolID,
}
if err != nil {
// 错误也作为结果返回
result.Text = fmt.Sprintf("Error: %v", err)
result.IsError = true
} else {
result.Text = output
}
results = append(results, result)
}
}
return results
}
这样,模型可以根据错误信息:
- 重试操作(例如:权限不足,尝试sudo)
- 尝试替代方案(例如:文件不存在,搜索其他路径)
- 向用户解释问题(例如:"该文件受保护,无法读取")
四、统一接口:屏蔽提供商差异
4.1 API差异的现实
主流AI提供商的工具调用格式存在显著差异:
Anthropic格式:
{
"content": [
{"type": "text", "text": "我来帮你读取文件"},
{
"type": "tool_use",
"id": "toolu_01A",
"name": "read_file",
"input": {"path": "/tmp/test.txt"}
}
],
"stop_reason": "tool_use"
}
OpenAI格式:
{
"message": {
"content": "我来帮你读取文件",
"tool_calls": [
{
"id": "call_abc123",
"function": {
"name": "read_file",
"arguments": "{\"path\": \"/tmp/test.txt\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
差异点包括:
- 工具调用位置:
content数组 vstool_calls字段 - 参数格式:对象 vs JSON字符串
- 停止原因:
stop_reasonvsfinish_reason - 系统提示词:单独字段 vs 消息数组第一项
4.2 Vikusha的适配器模式
Vikusha通过适配器模式解决这一问题:
// provider/anthropic/adapter.go
package anthropic
type Adapter struct {
client *http.Client
apiKey string
}
func (a *Adapter) Complete(ctx context.Context, messages []llm.Message, tools []llm.Tool) (*llm.Response, error) {
// 1. 转换请求格式
req := a.convertRequest(messages, tools)
// 2. 调用API
resp, err := a.callAPI(ctx, req)
if err != nil {
return nil, err
}
// 3. 转换响应格式
return a.convertResponse(resp), nil
}
func (a *Adapter) convertRequest(messages []llm.Message, tools []llm.Tool) *AnthropicRequest {
req := &AnthropicRequest{
Model: "claude-sonnet-4-20250514",
MaxTokens: 4096,
}
for _, msg := range messages {
if msg.Role == "system" {
req.System = extractText(msg)
} else {
req.Messages = append(req.Messages, a.convertMessage(msg))
}
}
req.Tools = a.convertTools(tools)
return req
}
func (a *Adapter) convertResponse(resp *AnthropicResponse) *llm.Response {
response := &llm.Response{}
for _, content := range resp.Content {
switch content.Type {
case "text":
response.Content = append(response.Content, llm.Block{
Type: llm.BlockText,
Text: content.Text,
})
case "tool_use":
response.Content = append(response.Content, llm.Block{
Type: llm.BlockToolUse,
ToolID: content.ID,
ToolName: content.Name,
ToolInput: content.Input,
})
}
}
return response
}
// provider/openai/adapter.go
package openai
type Adapter struct {
client *http.Client
apiKey string
}
func (a *Adapter) Complete(ctx context.Context, messages []llm.Message, tools []llm.Tool) (*llm.Response, error) {
req := a.convertRequest(messages, tools)
resp, err := a.callAPI(ctx, req)
if err != nil {
return nil, err
}
return a.convertResponse(resp), nil
}
func (a *Adapter) convertResponse(resp *OpenAIResponse) *llm.Response {
response := &llm.Response{}
// 处理文本内容
if resp.Message.Content != "" {
response.Content = append(response.Content, llm.Block{
Type: llm.BlockText,
Text: resp.Message.Content,
})
}
// 处理工具调用
for _, call := range resp.Message.ToolCalls {
var input map[string]interface{}
json.Unmarshal([]byte(call.Function.Arguments), &input)
response.Content = append(response.Content, llm.Block{
Type: llm.BlockToolUse,
ToolID: call.ID,
ToolName: call.Function.Name,
ToolInput: input,
})
}
return response
}
4.3 切换提供商的零成本
得益于统一接口,切换提供商只需修改初始化代码:
// 使用Anthropic
adapter := anthropic.NewAdapter(apiKey)
// 切换到OpenAI(通过OpenRouter)
adapter := openai.NewAdapter(openRouterKey, "https://openrouter.ai/api/v1")
// 智能体代码完全不变
agent := vikusha.New(adapter, tools, systemPrompt)
result, _ := agent.Run(ctx, userMessage)
五、实战:构建一个文件分析智能体
5.1 定义工具
首先,我们实现一个文件读取工具:
// tools/read_file.go
package tools
import (
"fmt"
"os"
)
type ReadFileTool struct{}
func (t *ReadFileTool) Name() string {
return "read_file"
}
func (t *ReadFileTool) Description() string {
return "读取指定路径的文件内容"
}
func (t *ReadFileTool) Schema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "要读取的文件路径",
},
},
"required": []string{"path"},
}
}
func (t *ReadFileTool) Run(input map[string]interface{}) (string, error) {
path, ok := input["path"].(string)
if !ok {
return "", fmt.Errorf("path must be a string")
}
content, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(content), nil
}
再实现一个目录列表工具:
// tools/list_directory.go
package tools
import (
"fmt"
"os"
"strings"
)
type ListDirectoryTool struct{}
func (t *ListDirectoryTool) Name() string {
return "list_directory"
}
func (t *ListDirectoryTool) Description() string {
return "列出指定目录下的所有文件和子目录"
}
func (t *ListDirectoryTool) Schema() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "目录路径",
},
},
"required": []string{"path"},
}
}
func (t *ListDirectoryTool) Run(input map[string]interface{}) (string, error) {
path, ok := input["path"].(string)
if !ok {
return "", fmt.Errorf("path must be a string")
}
entries, err := os.ReadDir(path)
if err != nil {
return "", err
}
var lines []string
for _, entry := range entries {
prefix := "📄 "
if entry.IsDir() {
prefix = "📁 "
}
lines = append(lines, prefix+entry.Name())
}
return strings.Join(lines, "\n"), nil
}
5.2 构建智能体
// main.go
package main
import (
"context"
"fmt"
"log"
"github.com/yourname/vikusha/llm"
"github.com/yourname/vikusha/provider/anthropic"
"github.com/yourname/vikusha/tools"
"github.com/yourname/vikusha/agent"
)
func main() {
// 1. 创建模型适配器
adapter := anthropic.NewAdapter(os.Getenv("ANTHROPIC_API_KEY"))
// 2. 注册工具
toolRegistry := map[string]llm.Tool{
"read_file": &tools.ReadFileTool{},
"list_directory": &tools.ListDirectoryTool{},
}
// 3. 设置系统提示词
systemPrompt := `你是一个文件分析助手。你可以:
- 读取文件内容
- 列出目录结构
- 分析代码结构
当用户请求分析某个目录时,先列出目录内容,然后根据文件类型选择性地读取关键文件。`
// 4. 创建智能体
ag := agent.New(adapter, toolRegistry, systemPrompt)
// 5. 运行
ctx := context.Background()
userMessage := "帮我分析当前目录的代码结构"
result, err := ag.Run(ctx, userMessage)
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
}
5.3 Agent核心实现
// agent/agent.go
package agent
import (
"context"
"fmt"
"github.com/yourname/vikusha/llm"
)
type Agent struct {
model llm.CompleteFunc
tools map[string]llm.Tool
systemPrompt string
maxTurns int
}
func New(model llm.CompleteFunc, tools map[string]llm.Tool, systemPrompt string) *Agent {
return &Agent{
model: model,
tools: tools,
systemPrompt: systemPrompt,
maxTurns: 50, // 防止无限循环
}
}
func (a *Agent) Run(ctx context.Context, userMessage string) (string, error) {
// 初始化消息历史
messages := []llm.Message{
{Role: "system", Content: []llm.Block{{Type: llm.BlockText, Text: a.systemPrompt}}},
{Role: "user", Content: []llm.Block{{Type: llm.BlockText, Text: userMessage}}},
}
// Agent Loop
for i := 0; i < a.maxTurns; i++ {
// 1. 调用模型
response, err := a.model(ctx, messages, a.toolList())
if err != nil {
return "", fmt.Errorf("model call failed: %w", err)
}
// 2. 检查是否需要继续
if !a.hasToolUse(response) {
// 无工具调用,返回最终答案
return a.extractText(response), nil
}
// 3. 追加assistant消息(包含所有内容)
messages = append(messages, llm.Message{
Role: "assistant",
Content: response.Content,
})
// 4. 执行工具
toolResults := a.executeTools(response)
// 5. 追加user消息(包含所有工具结果)
messages = append(messages, llm.Message{
Role: "user",
Content: toolResults,
})
}
return "", fmt.Errorf("exceeded maximum turns (%d)", a.maxTurns)
}
func (a *Agent) hasToolUse(response *llm.Response) bool {
for _, block := range response.Content {
if block.Type == llm.BlockToolUse {
return true
}
}
return false
}
func (a *Agent) extractText(response *llm.Response) string {
var texts []string
for _, block := range response.Content {
if block.Type == llm.BlockText {
texts = append(texts, block.Text)
}
}
return join(texts, "\n")
}
func (a *Agent) executeTools(response *llm.Response) []llm.Block {
var results []llm.Block
for _, block := range response.Content {
if block.Type != llm.BlockToolUse {
continue
}
tool, exists := a.tools[block.ToolName]
if !exists {
results = append(results, llm.Block{
Type: llm.BlockToolResult,
ToolResultID: block.ToolID,
Text: fmt.Sprintf("Error: unknown tool '%s'", block.ToolName),
IsError: true,
})
continue
}
output, err := tool.Run(block.ToolInput)
result := llm.Block{
Type: llm.BlockToolResult,
ToolResultID: block.ToolID,
}
if err != nil {
result.Text = fmt.Sprintf("Error: %v", err)
result.IsError = true
} else {
result.Text = output
}
results = append(results, result)
}
return results
}
func (a *Agent) toolList() []llm.Tool {
var list []llm.Tool
for _, tool := range a.tools {
list = append(list, tool)
}
return list
}
六、性能优化与生产级考量
6.1 并行工具执行
当模型返回多个工具调用时,可以并行执行以提升性能:
func (a *Agent) executeToolsParallel(response *llm.Response) []llm.Block {
var toolUses []llm.Block
for _, block := range response.Content {
if block.Type == llm.BlockToolUse {
toolUses = append(toolUses, block)
}
}
results := make([]llm.Block, len(toolUses))
var wg sync.WaitGroup
for i, block := range toolUses {
wg.Add(1)
go func(idx int, b llm.Block) {
defer wg.Done()
results[idx] = a.executeSingleTool(b)
}(i, block)
}
wg.Wait()
return results
}
6.2 流式输出支持
对于长时间运行的任务,流式输出可以提升用户体验:
type StreamEvent struct {
Type string // "text", "tool_start", "tool_end", "error"
Content string
}
func (a *Agent) RunStream(ctx context.Context, userMessage string, events chan<- StreamEvent) (string, error) {
defer close(events)
for {
response, _ := a.model(ctx, messages, a.toolList())
// 流式发送文本内容
for _, block := range response.Content {
if block.Type == llm.BlockText {
events <- StreamEvent{Type: "text", Content: block.Text}
}
}
if !a.hasToolUse(response) {
return a.extractText(response), nil
}
// 流式发送工具执行状态
for _, block := range response.Content {
if block.Type == llm.BlockToolUse {
events <- StreamEvent{
Type: "tool_start",
Content: fmt.Sprintf("Executing: %s", block.ToolName),
}
}
}
// ... 执行工具并发送 tool_end 事件
}
}
6.3 上下文长度管理
长对话可能导致上下文超出模型限制。解决方案:
func (a *Agent) truncateMessages(messages []llm.Message, maxTokens int) []llm.Message {
// 简单策略:保留系统提示词 + 最近N轮对话
if len(messages) <= 2 {
return messages
}
systemMessage := messages[0]
recentMessages := messages[len(messages)-a.config.ContextWindow:]
return append([]llm.Message{systemMessage}, recentMessages...)
}
更高级的策略包括:
- 摘要压缩:用模型总结早期对话,替换原始消息
- 重要性排序:保留关键决策点,丢弃闲聊内容
- 向量检索:将历史存入向量库,按需检索相关上下文
6.4 安全考量
工具执行是智能体的安全薄弱环节。Vikusha建议的安全措施:
// 工具白名单
var allowedTools = map[string]bool{
"read_file": true,
"list_directory": true,
// "execute_bash": false, // 禁用危险工具
}
// 路径限制
func (t *ReadFileTool) Run(input map[string]interface{}) (string, error) {
path := input["path"].(string)
// 防止路径遍历攻击
if strings.Contains(path, "..") {
return "", fmt.Errorf("path traversal not allowed")
}
// 限制在允许的目录内
absPath, _ := filepath.Abs(path)
if !strings.HasPrefix(absPath, t.allowedDir) {
return "", fmt.Errorf("access denied: path outside allowed directory")
}
// ... 执行读取
}
// 超时控制
func (a *Agent) executeToolWithTimeout(tool llm.Tool, input map[string]interface{}) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resultCh := make(chan struct {
result string
err error
})
go func() {
result, err := tool.Run(input)
resultCh <- struct {
result string
err error
}{result, err}
}()
select {
case <-ctx.Done():
return "", fmt.Errorf("tool execution timeout")
case res := <-resultCh:
return res.result, res.err
}
}
七、与其他框架的对比
7.1 Vikusha vs LangChain
| 维度 | Vikusha | LangChain |
|---|---|---|
| 核心代码量 | ~500行 | 数万行 |
| 学习曲线 | 平缓(理解Agent Loop即可) | 陡峭(需学习大量抽象) |
| 灵活性 | 极高(用户定义一切) | 中等(需遵循框架约定) |
| 性能 | 高(无额外抽象开销) | 中(多层抽象带来开销) |
| 生态丰富度 | 低(需自行扩展) | 高(内置大量集成) |
| 调试难度 | 低(逻辑透明) | 高(需穿越多层抽象) |
7.2 Vikusha vs DeerFlow
| 维度 | Vikusha | DeerFlow |
|---|---|---|
| 定位 | 极简框架(教学/理解) | 生产级框架(企业应用) |
| 语言 | Go | Python |
| 工具链 | 需自行实现 | 内置丰富工具 |
| 任务规划 | 无(用户实现) | 有(Plan/Agent/YOLO模式) |
| 多智能体 | 无 | 有(RLM调度) |
| 适用场景 | 学习、原型、轻量应用 | 企业级复杂工作流 |
7.3 选择建议
- 选择Vikusha:如果你想理解智能体原理、快速原型验证、构建轻量级应用
- 选择LangChain:如果你需要丰富的集成、不想重复造轮子、团队协作开发
- 选择DeerFlow:如果你需要处理复杂的多步骤任务、企业级生产部署
八、未来展望
Vikusha当前版本已支持基础的文件操作。下一步的发展方向包括:
8.1 Bash工具:从"只读"到"可执行"
Bash工具的核心挑战不在于实现,而在于安全封装:
type BashTool struct {
allowedCommands map[string]bool
timeout time.Duration
outputLimit int
}
func (t *BashTool) Run(input map[string]interface{}) (string, error) {
command := input["command"].(string)
// 1. 解析命令
cmd, args := parseCommand(command)
// 2. 白名单检查
if !t.allowedCommands[cmd] {
return "", fmt.Errorf("command '%s' not allowed", cmd)
}
// 3. 危险操作拦截
if containsDangerousPattern(command) {
return "", fmt.Errorf("dangerous operation detected")
}
// 4. 执行(带超时和输出限制)
ctx, cancel := context.WithTimeout(context.Background(), t.timeout)
defer cancel()
output, err := exec.CommandContext(ctx, cmd, args...).CombinedOutput()
// 5. 截断输出
if len(output) > t.outputLimit {
output = output[:t.outputLimit]
output = append(output, []byte("\n... (output truncated)")...)
}
return string(output), err
}
8.2 多智能体协作
虽然Vikusha本身是单智能体框架,但可以通过组合实现多智能体:
type Orchestrator struct {
agents map[string]*Agent
router func(task string) string // 任务路由函数
}
func (o *Orchestrator) Run(ctx context.Context, task string) (string, error) {
// 1. 路由到合适的智能体
agentName := o.router(task)
agent := o.agents[agentName]
// 2. 执行
return agent.Run(ctx, task)
}
// 示例:代码分析智能体编排
orchestrator := &Orchestrator{
agents: map[string]*Agent{
"file_reader": fileReaderAgent,
"code_analyzer": codeAnalyzerAgent,
"doc_writer": docWriterAgent,
},
router: func(task string) string {
if strings.Contains(task, "读取") || strings.Contains(task, "查看") {
return "file_reader"
}
if strings.Contains(task, "分析") || strings.Contains(task, "检查") {
return "code_analyzer"
}
return "doc_writer"
},
}
8.3 持久化与记忆
将对话历史持久化,实现跨会话记忆:
type PersistentAgent struct {
*Agent
storage MessageStorage
sessionID string
}
func (a *PersistentAgent) Run(ctx context.Context, userMessage string) (string, error) {
// 1. 加载历史
history, _ := a.storage.Load(a.sessionID)
// 2. 运行智能体
result, err := a.Agent.RunWithHistory(ctx, userMessage, history)
// 3. 保存历史
a.storage.Save(a.sessionID, a.Agent.GetHistory())
return result, err
}
总结
Vikusha用不到500行Go代码,揭示了AI智能体的核心秘密:Agent Loop。
这个循环的本质是:
- 模型决策:根据消息历史和可用工具,决定下一步行动
- 工具执行:如果模型发起工具调用,执行并收集结果
- 迭代继续:将工具结果反馈给模型,重复上述过程
理解了这个循环,智能体就不再神秘。它不是某种神奇的"推理引擎",而是一个确定性的、可观测的、可调试的迭代过程。
Vikusha的设计哲学——极简核心 + 用户定义——为我们提供了一个思考框架:
- 框架应该做什么?管理循环、屏蔽差异、提供类型
- 用户应该做什么?定义提示词、实现工具、配置传输
这种清晰的边界划分,使得Vikusha既适合学习理解,也适合快速原型开发。
当然,对于生产级应用,你可能需要更丰富的工具链、更完善的错误处理、更复杂的多智能体协作。这时,DeerFlow、LangChain等框架可能更合适。
但无论如何,理解Agent Loop,是理解所有AI智能体框架的第一步。而Vikusha,是通往这个理解的最佳起点。
参考资源:
- Vikusha GitHub仓库:搜索 "Vikusha Go Agent"
- Anthropic Tool Use文档:https://docs.anthropic.com/claude/docs/tool-use
- OpenAI Function Calling文档:https://platform.openai.com/docs/guides/function-calling