AI赋能业务工作流:极简架构下的落地策略与工程实践

cover

一、AI落地的两难:技术先进性与工程简洁性的冲突

AI能力接入业务系统时,架构师面临一个核心矛盾:技术团队倾向于引入最先进的模型和框架,而业务团队只关心"能不能用、稳不稳定、贵不贵"。一个RAG系统,用LangChain+Chroma+OpenAI可以快速搭出原型,但生产部署时发现依赖链太长、调试困难、成本不可控。另一个极端是直接调API拼Prompt,简单粗暴但缺乏可维护性。

真正的挑战在于:如何在保持架构极简的前提下,将AI能力稳定地融入现有业务工作流?极简不是偷工减料,而是用最少的组件实现最核心的能力,同时为未来的扩展留出接口。

二、AI工作流集成的极简架构:三层解耦模型

将AI能力集成到业务系统,最有效的架构是三层解耦:接入层、编排层、执行层。每层只做一件事,层与层之间通过明确的接口通信。

graph TB
    subgraph 接入层
        A1[REST API] --> B[AI网关]
        A2[消息队列] --> B
        A3[定时任务] --> B
    end

    subgraph 编排层
        B --> C[Prompt模板引擎]
        C --> D[上下文管理器]
        D --> E[结果校验器]
    end

    subgraph 执行层
        E --> F1[OpenAI]
        E --> F2[Claude]
        E --> F3[本地模型]
    end

    subgraph 可观测性
        G[Token计量] -.-> B
        H[延迟监控] -.-> E
        I[质量评分] -.-> E
    end

    style B fill:#722ed1,color:#fff
    style C fill:#1890ff,color:#fff
    style D fill:#faad14,color:#fff
    style E fill:#52c41a,color:#fff

接入层统一所有AI请求的入口,无论来自API调用、消息消费还是定时触发,都经过AI网关进行鉴权、限流和路由。编排层是核心,负责Prompt模板渲染、上下文窗口管理和输出校验。执行层是多模型适配层,屏蔽不同LLM提供商的API差异。

这种架构的关键在于:业务代码不直接调用LLM API,而是通过AI网关的统一接口。LLM提供商的切换、Prompt的优化、上下文策略的调整,都在编排层完成,业务层无感知。

三、极简AI网关的Go实现

以下是一个生产可用的AI网关核心实现,强调模型切换、Prompt模板化和结果校验:

package gateway

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"sync"
	"text/template"
	"time"
)

// LLMProvider 定义模型提供商接口
type LLMProvider interface {
	Name() string
	Chat(ctx context.Context, req *ChatRequest) (*ChatResponse, error)
	EstimateTokens(text string) int
}

// ChatRequest 统一的聊天请求
type ChatRequest struct {
	Messages []Message `json:"messages"`
	Model    string    `json:"model"`
	// 最大生成Token数
	MaxTokens int `json:"max_tokens,omitempty"`
	// 温度参数
	Temperature float64 `json:"temperature,omitempty"`
}

// ChatResponse 统一的聊天响应
type ChatResponse struct {
	Content     string `json:"content"`
	Model       string `json:"model"`
	TokensUsed  int    `json:"tokens_used"`
	LatencyMs   int64  `json:"latency_ms"`
	FinishReason string `json:"finish_reason"`
}

// Message 对话消息
type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

// PromptTemplate Prompt模板定义
type PromptTemplate struct {
	Name     string
	Template string
	// 模板参数的JSON Schema,用于校验输入
	ParamSchema json.RawMessage
}

// AIGateway AI网关
type AIGateway struct {
	providers map[string]LLMProvider
	templates map[string]*PromptTemplate
	// 默认模型提供商
	defaultProvider string
	mu              sync.RWMutex
	// Token计量
	tokenCounter *TokenCounter
}

// TokenCounter Token消耗计量器
type TokenCounter struct {
	dailyUsage map[string]int // key: date+provider, value: token count
	mu         sync.Mutex
}

// NewAIGateway 创建AI网关
func NewAIGateway(defaultProvider string) *AIGateway {
	return &AIGateway{
		providers:      make(map[string]LLMProvider),
		templates:      make(map[string]*PromptTemplate),
		defaultProvider: defaultProvider,
		tokenCounter:   &TokenCounter{dailyUsage: make(map[string]int)},
	}
}

// RegisterProvider 注册模型提供商
func (g *AIGateway) RegisterProvider(provider LLMProvider) {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.providers[provider.Name()] = provider
}

// RegisterTemplate 注册Prompt模板
func (g *AIGateway) RegisterTemplate(tpl *PromptTemplate) {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.templates[tpl.Name()] = tpl
}

// Execute 执行AI请求,支持模板渲染和模型切换
func (g *AIGateway) Execute(ctx context.Context, req *ExecuteRequest) (*ChatResponse, error) {
	// 1. 渲染Prompt模板
	messages, err := g.renderTemplate(req.TemplateName, req.TemplateParams)
	if err != nil {
		return nil, fmt.Errorf("模板渲染失败: %w", err)
	}

	// 2. 注入上下文(如果有)
	if req.Context != "" {
		messages = g.injectContext(messages, req.Context)
	}

	// 3. 选择模型提供商
	providerName := req.Provider
	if providerName == "" {
		providerName = g.defaultProvider
	}

	g.mu.RLock()
	provider, exists := g.providers[providerName]
	g.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("未注册的模型提供商: %s", providerName)
	}

	// 4. Token预算检查
	estimatedTokens := 0
	for _, msg := range messages {
		estimatedTokens += provider.EstimateTokens(msg.Content)
	}
	if req.MaxTokens > 0 && estimatedTokens > req.MaxTokens {
		// 截断上下文以适应Token预算
		messages = g.truncateMessages(messages, provider, req.MaxTokens)
	}

	// 5. 调用模型
	startTime := time.Now()
	chatReq := &ChatRequest{
		Messages:    messages,
		Model:       req.Model,
		MaxTokens:   req.MaxOutputTokens,
		Temperature: req.Temperature,
	}

	resp, err := provider.Chat(ctx, chatReq)
	if err != nil {
		// 主模型失败,尝试降级到备用模型
		if req.FallbackProvider != "" {
			return g.executeFallback(ctx, req.FallbackProvider, chatReq)
		}
		return nil, fmt.Errorf("模型调用失败: %w", err)
	}
	resp.LatencyMs = time.Since(startTime).Milliseconds()

	// 6. 结果校验
	if req.Validator != nil {
		if err := req.Validator(resp.Content); err != nil {
			return nil, fmt.Errorf("结果校验失败: %w", err)
		}
	}

	// 7. 记录Token消耗
	g.tokenCounter.Record(providerName, resp.TokensUsed)

	return resp, nil
}

// renderTemplate 渲染Prompt模板
func (g *AIGateway) renderTemplate(name string, params map[string]any) ([]Message, error) {
	g.mu.RLock()
	tpl, exists := g.templates[name]
	g.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("模板不存在: %s", name)
	}

	tmpl, err := template.New(name).Parse(tpl.Template)
	if err != nil {
		return nil, fmt.Errorf("模板解析失败: %w", err)
	}

	var buf strings.Builder
	if err := tmpl.Execute(&buf, params); err != nil {
		return nil, fmt.Errorf("模板执行失败: %w", err)
	}

	// 解析渲染后的内容为消息列表
	var messages []Message
	if err := json.Unmarshal([]byte(buf.String()), &messages); err != nil {
		// 如果不是JSON格式,作为单条用户消息处理
		messages = []Message{{Role: "user", Content: buf.String()}}
	}

	return messages, nil
}

// injectContext 将业务上下文注入到消息列表
func (g *AIGateway) injectContext(messages []Message, ctx string) []Message {
	contextMsg := Message{
		Role:    "system",
		Content: fmt.Sprintf("以下是相关的业务上下文信息:\n%s", ctx),
	}
	// 插入到系统消息之后、用户消息之前
	result := make([]Message, 0, len(messages)+1)
	inserted := false
	for _, msg := range messages {
		result = append(result, msg)
		if msg.Role == "system" && !inserted {
			result = append(result, contextMsg)
			inserted = true
		}
	}
	if !inserted {
		result = append([]Message{contextMsg}, result...)
	}
	return result
}

// truncateMessages 按Token预算截断消息
func (g *AIGateway) truncateMessages(
	messages []Message, provider LLMProvider, budget int,
) []Message {
	totalTokens := 0
	result := make([]Message, 0, len(messages))
	// 从最早的消息开始丢弃,保留最近的
	for i := len(messages) - 1; i >= 0; i-- {
		tokens := provider.EstimateTokens(messages[i].Content)
		if totalTokens+tokens > budget {
			break
		}
		totalTokens += tokens
		result = append([]Message{messages[i]}, result...)
	}
	return result
}

// executeFallback 降级到备用模型
func (g *AIGateway) executeFallback(
	ctx context.Context, providerName string, req *ChatRequest,
) (*ChatResponse, error) {
	g.mu.RLock()
	provider, exists := g.providers[providerName]
	g.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("备用模型未注册: %s", providerName)
	}
	return provider.Chat(ctx, req)
}

// ExecuteRequest 执行请求参数
type ExecuteRequest struct {
	TemplateName    string         // Prompt模板名称
	TemplateParams  map[string]any // 模板参数
	Context         string         // 业务上下文
	Provider        string         // 模型提供商
	Model           string         // 具体模型
	FallbackProvider string        // 降级模型提供商
	MaxTokens       int            // Token预算上限
	MaxOutputTokens int            // 最大输出Token数
	Temperature     float64        // 温度参数
	Validator       func(string) error // 结果校验函数
}

// Record 记录Token消耗
func (tc *TokenCounter) Record(provider string, tokens int) {
	tc.mu.Lock()
	defer tc.mu.Unlock()
	key := fmt.Sprintf("%s:%s", time.Now().Format("2006-01-02"), provider)
	tc.dailyUsage[key] += tokens
}

四、极简架构的边界:何时需要更重的方案

三层解耦模型在中小规模场景下非常有效,但当系统规模增长到一定程度时,它的局限性会暴露出来。

缺乏流式输出支持。当前实现只支持同步请求-响应模式,不支持SSE流式输出。对于长文本生成场景(如文章撰写、代码生成),用户需要等待整个响应完成才能看到结果,体验很差。要支持流式输出,需要将ChatResponse改为channel模式,这会增加编排层的复杂度。

不支持多轮对话的持久化。上下文管理器只维护单次请求的上下文,不负责跨请求的对话历史持久化。如果你的业务需要多轮对话(如客服机器人),需要在外部维护对话session,并在每次请求时传入完整历史。

Prompt版本管理缺失。模板以代码形式硬编码,没有版本管理和A/B测试能力。在生产环境中,Prompt的调优是持续进行的,你需要能够灰度发布新Prompt、对比不同版本的效果。这需要一个独立的Prompt管理服务。

禁用场景:需要实时流式输出的对话产品——当前架构不支持SSE;需要严格合规审核的AI输出(如金融、医疗)——校验器过于简单,无法满足合规要求;多租户场景下需要Prompt隔离和用量配额——当前网关没有租户概念。

五、总结

AI赋能业务工作流的核心策略是"三层解耦":接入层统一入口、编排层管理Prompt和上下文、执行层屏蔽模型差异。这种极简架构的优势在于:业务代码与AI实现完全解耦,模型切换和Prompt优化可以在不影响业务的情况下进行。Go实现的AI网关提供了模板渲染、Token预算控制、降级切换和结果校验等生产级能力。但极简架构有其边界——当需要流式输出、多轮对话持久化或Prompt版本管理时,就需要引入更重的组件。架构的演进应该是渐进的,从极简开始,按需加码。

Logo

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

更多推荐