系列导航:第一篇:什么是Loop | 第二篇:设计你的第一个Loop | 第三篇:反馈循环设计 | 第四篇:自我修复Loop | 第五篇:生产环境Loop | 第六篇:完整实战
引言:从玩具到生产
在前四篇中,我们从认识Loop Engineering开始,一步步设计了第一个文本改进Loop,深入学习了反馈循环设计和自我修复机制。这些知识让我们能够在本地环境中跑通一个完整的AI Loop流程。
但当你把一个在Jupyter Notebook里跑得好好的Loop搬到生产环境时,你会突然发现——事情远没有那么简单。
本地环境下,你关心的是”能不能跑通”。生产环境下,你需要回答一连串全新的问题:
- 延迟:用户等了30秒还没收到响应,他会关掉页面。你的Loop能在10秒内完成吗?
- 成本:每天10万次请求,每次消耗2000个Token,一个月的API费用是多少?老板问你要预算,你能报出来吗?
- 可靠性:凌晨3点OpenAI的API挂了,你的Loop怎么办?直接报错还是优雅降级?
- 并发:突然涌入1000个并发请求,你的系统会不会崩溃?
- 可观测性:一个用户投诉”结果不对”,你能在1分钟内定位到是哪个环节出了问题吗?
这些问题,就是本篇要解决的核心议题。
生产环境Loop和开发环境Loop的本质区别是什么? 不是代码复杂度,而是你必须在不确定性中建立确定性。AI模型的输出天生具有随机性,网络调用天生具有不稳定性,用户请求天生具有突发性。生产环境Loop的工程,就是在这些不确定性之上,构建一个用户可信赖、业务可持续的系统。
本篇不会讨论具体的框架或云平台选型——那些会过时。我们要讨论的是生产环境Loop的核心工程原则和策略模式,这些原则无论你用什么技术栈都适用。
生产环境挑战
在深入解决方案之前,让我们先完整地理解问题本身。生产环境Loop面临四大核心挑战:
2.1 延迟
延迟是用户体验的第一杀手。一个典型的Loop可能包含多次LLM调用、多次工具调用、以及循环迭代。假设每次LLM调用需要2-5秒,工具调用需要0.5-2秒,一个3轮迭代的Loop就可能需要10-20秒。
延迟的来源是多层次的:
网络延迟:你的服务器到LLM API的数据中心之间的物理距离。跨洲调用可能增加100-200ms的基础延迟。
排队延迟:LLM API不是为你一个人服务的。在高峰期,你的请求可能需要排队等待GPU资源。这在GPT-4等热门模型上尤其明显,排队时间可能从几秒到几十秒不等。
推理延迟:模型生成Token的速度。这取决于模型大小、输出长度、以及底层硬件。一般来说,大模型更慢,长输出更慢。
迭代延迟:Loop的多轮迭代是延迟的主要放大器。每一轮迭代都是一次完整的LLM调用加上可能的工具调用。3轮迭代的延迟不是单次调用的3倍——因为每轮还增加了额外的处理逻辑和条件判断开销。
序列化延迟:随着Loop迭代,上下文窗口不断增长。更长的上下文意味着更多的Token需要处理,每一轮的推理延迟都会比上一轮更长。这是一个正反馈循环——迭代越多,每轮越慢。
2.2 成本
AI应用的成本结构和传统应用完全不同。传统应用的主要成本是服务器和存储,这些是相对固定和可预测的。AI应用的主要成本是Token消耗,它与请求数量和复杂度成正比,而且价格不菲。
成本的挑战在于其非线性增长:
- Loop每多迭代一轮,成本就多一次LLM调用的费用。
- 上下文累积导致每轮的输入Token数递增。
- 失败重试会额外消耗Token。
- 复杂的工具调用可能产生额外的API费用(比如调用搜索引擎、代码执行器等)。
一个设计不当的Loop,单次请求可能消耗数万个Token,成本可能达到几美元。当你有十万用户时,这就变成了一个严重的财务问题。
2.3 可靠性
传统应用的可靠性主要取决于你自己的代码和基础设施。AI应用的可靠性还额外依赖于外部LLM API的可用性,而这些API的SLA(服务等级协议)通常远不如你自己的系统。
可靠性威胁包括:
API不可用:LLM API可能因为维护、过载、或故障而暂时不可用。OpenAI、Anthropic等服务偶尔会出现服务中断。
API限流:当你的请求频率超过配额时,API会返回429 Too Many Requests错误。在业务高峰期,多个服务共享同一个API Key时,限流尤其容易触发。
模型行为变化:LLM的更新可能导致同一Prompt产生不同质量的输出。你精心调优的Prompt在新版本模型上可能表现退化。
上下文溢出:随着Loop迭代,上下文可能超出模型的上下文窗口限制。这在长对话场景中尤其常见。
幻觉和错误输出:模型可能产生错误的、不相关的、或格式不符合预期的输出。在无人值守的生产环境中,这些错误如果不被检测和处理,会直接影响用户体验。
2.4 并发
生产环境面对的是真实用户流量,具有突发性和不可预测性。
流量突发:一个营销活动可能在几分钟内带来10倍的流量峰值。如果你的Loop没有限流和排队机制,系统可能直接崩溃。
资源竞争:多个并发Loop可能竞争同一个API Key的配额、同一个数据库连接池、同一个缓存。不当的资源管理会导致死锁、饥饿、或性能退化。
状态管理:有状态的Loop在并发环境下尤其复杂。你需要确保每个用户的Loop实例有独立的上下文,不会互相干扰。
长尾延迟:在高并发下,少数请求的延迟可能远高于平均水平(通常是正常延迟的5-10倍)。这些长尾请求如果不被妥善处理,会影响整体的服务质量指标。
Loop调度策略
调度策略决定了Loop的迭代如何被安排和执行。不同的场景需要不同的调度策略。
3.1 固定调度
最简单的策略是固定轮次调度——Loop迭代固定次数后停止。
`
策略:最多执行N轮迭代
适用:任务复杂度可预测、对延迟敏感的场景
示例:简单的内容生成Loop,最多3轮自我修正
`
固定调度的优点是行为可预测——你能精确计算最坏情况下的延迟和成本上限。缺点是不灵活——简单的任务可能不需要那么多轮迭代就被强制终止了,复杂任务可能在达到上限时仍未完成。
在实践中,固定调度通常与质量检查结合使用:设置一个最大轮次(比如5轮),但在每轮迭代后检查输出质量,如果质量达标就提前终止。这样既保证了上限,又避免了不必要的浪费。
`python
MAX_ITERATIONS = 5
def fixed_schedule_loop(task):
result = initial_generation(task)
for i in range(MAX_ITERATIONS):
quality = evaluate_quality(result)
if quality >= THRESHOLD:
return result
result = improve(result, feedback)
return result # 返回最后一次结果,即使质量未达标
`
3.2 自适应调度
自适应调度根据每轮迭代的反馈动态决定是否继续以及如何继续。
核心思想是:让Loop自己决定需要多少轮迭代。这比固定调度更高效,因为简单任务快速完成,复杂任务获得足够的迭代空间。
自适应调度的决策因素包括:
- 质量分数变化率:如果连续两轮的质量提升很小(边际收益递减),说明继续迭代的收益有限,应该停止。
- Token消耗趋势:如果每轮消耗的Token在快速增长,需要考虑成本上限。
- 错误频率:如果连续出现错误,继续迭代可能无法改善结果。
- 时间预算:已经消耗的时间与总时间预算的比值。
`python
def adaptive_schedule_loop(task, time_budget=30, token_budget=10000):
result = initial_generation(task)
prev_quality = 0
total_tokens = 0
while total_tokens < token_budget and elapsed() < time_budget:
quality = evaluate_quality(result)
# 质量足够好,提前退出
if quality >= HIGH_THRESHOLD:
return result
# 质量提升太小,边际收益递减
if quality – prev_quality = MIN_THRESHOLD:
return result
prev_quality = quality
result, tokens = improve_with_tracking(result)
total_tokens += tokens
return result
`
3.3 优先级调度
当系统同时运行多个Loop时,需要根据优先级分配资源。
优先级调度的核心原则:
- 付费用户优先:商业逻辑决定了付费用户的Loop应该获得更快的响应和更多的迭代预算。
- 紧急任务优先:某些场景下(如安全扫描、实时客服),特定任务需要低延迟响应。
- 公平调度:在相同优先级的任务之间,使用公平调度避免个别任务长期占用资源。
`python
import heapq
from dataclasses import dataclass, field
@dataclass
class LoopTask:
priority: int # 数值越小优先级越高
created_at: float
task_id: str
payload: dict
def __lt__(self, other):
return self.priority < other.priority
class PriorityScheduler:
def __init__(self, max_concurrent=10):
self.queue = []
self.active = {}
self.max_concurrent = max_concurrent
def submit(self, task: LoopTask):
heapq.heappush(self.queue, task)
def get_next(self):
if len(self.active) >= self.max_concurrent:
return None
if self.queue:
task = heapq.heappop(self.queue)
self.active[task.task_id] = task
return task
return None
`
3.4 混合策略
实践中,大多数生产系统使用混合策略:用优先级调度决定哪个任务先执行,用自适应调度决定单个任务执行多少轮。
`
用户请求 → 优先级评估 → 进入调度队列
↓
获取执行机会 → 自适应Loop执行
↓
质量达标 / 达到上限 → 返回结果
`
这种分层设计让系统的宏观资源分配和微观执行策略解耦,各自独立优化。
并发与队列管理
并发管理是生产Loop系统的核心基础设施。目标是:在保护系统稳定性的前提下,最大化吞吐量。
4.1 背压机制
背压(Backpressure)是处理流量突增的核心机制。当请求到达速度超过处理速度时,系统需要主动”推回”多余的请求,而不是无限制地接受。
`
请求到达速率: ████████████████████ 1000 req/s
处理能力: ████████ 400 req/s
↓ 背压
实际接受: ████████ 400 req/s
排队等待: ████████ 排队中
拒绝/降级: ████ 返回降级结果
`
背压的实现方式:
队列长度限制:当队列满时,新请求被拒绝(返回503 Service Unavailable)或进入降级路径。
速率限制:基于Token Bucket或Leaky Bucket算法,限制每秒接受的请求数。
自适应限流:根据当前系统的处理延迟动态调整接受速率。如果平均延迟上升,说明系统接近饱和,应该降低接受速率。
4.2 任务队列设计
生产级Loop通常使用消息队列(如Redis、RabbitMQ、Kafka)来解耦请求接收和Loop执行。
`
[API Gateway] → [消息队列] → [Loop Worker 集群] → [结果存储] → [回调/轮询]
↑ ↑
接收请求 异步执行
立即返回 处理任务
任务ID 写入结果
`
这种异步架构的优势:
- 削峰填谷:队列作为缓冲区,平滑处理流量波动。
- 弹性伸缩:Worker数量可以根据队列深度动态调整。
- 故障隔离:单个Worker崩溃不影响整体系统,队列中的任务会被其他Worker接管。
- 重试友好:失败的任务可以重新入队重试。
4.3 超时与取消
生产环境的Loop必须有超时机制。没有超时的Loop是定时炸弹——一个陷入无限循环的Loop会永远占用Worker资源。
超时需要分层设计:
- 单次LLM调用超时:30-60秒。如果单次API调用超过这个时间,可能是API端出了问题。
- 单轮迭代超时:60-120秒。一轮迭代可能包含多次工具调用和一次LLM调用。
- 整个Loop超时:5-10分钟。整个Loop的总执行时间上限。
`python
import asyncio
class LoopWithTimeout:
def __init__(self,
call_timeout=60,
iteration_timeout=120,
total_timeout=600):
self.call_timeout = call_timeout
self.iteration_timeout = iteration_timeout
self.total_timeout = total_timeout
async def run(self, task):
try:
return await asyncio.wait_for(
self._execute_loop(task),
timeout=self.total_timeout
)
except asyncio.TimeoutError:
return self._graceful_timeout_result(task)
async def _execute_loop(self, task):
result = await self._init(task)
while not self._should_stop(result):
result = await asyncio.wait_for(
self._iterate(result),
timeout=self.iteration_timeout
)
return result
async def _call_llm(self, prompt):
return await asyncio.wait_for(
self._raw_llm_call(prompt),
timeout=self.call_timeout
)
`
取消机制同样重要。用户可能在Loop执行过程中取消请求(比如关闭页面)。系统需要能够及时取消正在执行的Loop,释放资源。
成本控制
成本控制是生产Loop能否持续运营的关键。没有成本控制的Loop,可能在一个月内烧掉你全年的预算。
5.1 Token预算管理
Token预算是成本控制的第一道防线。每个Loop实例应该有一个Token预算上限,超过预算就停止迭代。
`python
class TokenBudget:
def __init__(self, max_tokens):
self.max_tokens = max_tokens
self.used_tokens = 0
def consume(self, tokens):
self.used_tokens += tokens
if self.remaining <= 0:
raise BudgetExhausted(f”Token预算耗尽: {self.used_tokens}/{self.max_tokens}”)
@property
def remaining(self):
return max(0, self.max_tokens – self.used_tokens)
@property
def utilization(self):
return self.used_tokens / self.max_tokens
class BudgetAwareLoop:
def __init__(self, budget: TokenBudget):
self.budget = budget
async def call_llm(self, prompt, model=”gpt-4″):
estimated_tokens = estimate_tokens(prompt) + EXPECTED_OUTPUT_TOKENS
if estimated_tokens > self.budget.remaining:
# 切换到更便宜的模型,或者提前终止
raise BudgetExhausted(“剩余Token不足以完成调用”)
result = await self._raw_call(prompt, model)
self.budget.consume(result.usage.total_tokens)
return result
`
预算管理还需要考虑预测——在Loop开始前就估算总成本,并在用户提交请求时给出成本预估。这让用户对成本有预期,也让你的系统能够做更精确的资源规划。
5.2 模型路由
不是所有任务都需要最强的模型。模型路由(Model Routing)根据任务复杂度选择合适的模型,是成本优化的核心策略。
`
任务进入 → 复杂度评估 → 路由决策
↓
简单任务 → GPT-3.5 / Claude Haiku (便宜、快)
中等任务 → GPT-4o / Claude Sonnet (平衡)
复杂任务 → GPT-4 / Claude Opus (强、贵)
关键任务 → 多模型投票 (最可靠)
`
复杂度评估的依据:
- 任务类型:分类、摘要等结构化任务通常可以用更小的模型;创意写作、代码生成等需要更大的模型。
- 输入长度:短输入通常对应简单任务。
- 历史表现:如果你有历史数据,可以训练一个简单的分类器来预测任务需要什么级别的模型。
`python
class ModelRouter:
def __init__(self):
self.models = {
“simple”: {“model”: “gpt-3.5-turbo”, “cost_per_1k”: 0.002},
“medium”: {“model”: “gpt-4o”, “cost_per_1k”: 0.01},
“complex”: {“model”: “gpt-4”, “cost_per_1k”: 0.06},
}
def route(self, task):
complexity = self.assess_complexity(task)
tier = self.complexity_to_tier(complexity)
return self.models[tier]
def assess_complexity(self, task):
# 基于规则的简单评估
score = 0
if len(task.input) > 2000: score += 1
if task.requires_reasoning: score += 2
if task.requires_code: score += 1
if task.requires_creativity: score += 1
return score
`
一个更高级的策略是级联路由:先用便宜的模型尝试,如果输出质量不达标,再用更强的模型重试。这样大部分简单任务用便宜模型处理,只有少数复杂任务需要昂贵的模型。
5.3 缓存策略
缓存是成本优化的另一个关键手段。对于重复或相似的请求,直接返回缓存结果可以节省大量Token。
精确缓存:相同输入完全匹配时直接返回。适合模板化、结构化的任务。
语义缓存:使用向量相似度匹配相似请求。适合自然语言查询场景,比如客服问答。
`python
import hashlib
import numpy as np
class SemanticCache:
def __init__(self, similarity_threshold=0.95):
self.cache = {} # key -> (embedding, result, timestamp)
self.threshold = similarity_threshold
def get(self, query_embedding):
best_match = None
best_similarity = 0
for key, (cached_emb, result, ts) in self.cache.items():
similarity = cosine_similarity(query_embedding, cached_emb)
if similarity > best_similarity:
best_similarity = similarity
best_match = result
if best_similarity >= self.threshold:
return best_match
return None
def put(self, key, query_embedding, result):
self.cache[key] = (query_embedding, result, time.time())
`
缓存需要设定合理的过期策略。AI场景下,缓存的”新鲜度”取决于业务需求:客服问答的缓存可以存活更久(几小时到几天),而实时数据分析的缓存可能只存活几分钟。
监控与告警
你无法优化你看不到的东西。监控是生产Loop的生命线。
6.1 关键指标
生产Loop需要监控的核心指标分为四类:
性能指标:
- Loop端到端延迟(P50、P95、P99)
- 每轮迭代延迟
- LLM API调用延迟
- 工具调用延迟
- 队列等待时间
成本指标:
- 每次请求的Token消耗
- 每次请求的API费用
- 日/周/月总成本
- 缓存命中率(命中率越高,成本越低)
质量指标:
- 输出质量评分(需要人工标注或自动化评估)
- 用户满意度(显式反馈或隐式信号)
- Loop提前终止率(越高说明任务越容易完成)
- 错误率和重试率
系统指标:
- 并发活跃Loop数
- 队列深度
- Worker利用率
- API Key配额使用率
6.2 可观测性设计
生产Loop的可观测性需要在设计时就考虑,而不是事后补救。
结构化日志:每一次Loop迭代都应该产出结构化的日志,包含足够的上下文信息。
`python
import structlog
logger = structlog.get_logger()
async def loop_iteration(task_id, iteration, state):
start_time = time.time()
logger.info(“iteration_start”,
task_id=task_id,
iteration=iteration,
context_length=len(state.context),
token_budget_remaining=state.budget.remaining
)
try:
result = await execute_iteration(state)
logger.info(“iteration_complete”,
task_id=task_id,
iteration=iteration,
quality_score=result.quality,
tokens_used=result.tokens,
duration_ms=(time.time() – start_time) * 1000,
model_used=result.model,
tool_calls=result.tool_calls
)
return result
except Exception as e:
logger.error(“iteration_failed”,
task_id=task_id,
iteration=iteration,
error_type=type(e).__name__,
error_message=str(e),
duration_ms=(time.time() – start_time) * 1000
)
raise
`
分布式追踪:在微服务架构下,一个Loop可能跨越多个服务。分布式追踪(如OpenTelemetry)让你能够追踪一个请求从进入到完成的完整路径。
成本追踪:为每个请求、每个用户、每个业务线建立成本归因。当你发现总成本上升时,能快速定位是哪个环节导致的。
6.3 告警策略
告警应该是可操作的——每一条告警都应该对应一个明确的行动。
| 告警条件 | 级别 | 行动 |
|---|
|———|——|——|
| API错误率 > 5% | 警告 | 检查API状态 |
|---|---|---|
| API错误率 > 20% | 严重 | 启用降级策略 |
| P99延迟 > 30秒 | 警告 | 检查是否需要优化 |
| 日成本超出预算120% | 警告 | 检查异常请求 |
| 队列深度 > 1000 | 严重 | 扩容Worker |
| 单请求Token > 50000 | 警告 | 检查是否陷入循环 |
告警疲劳是监控系统的最大敌人。如果告警太多,团队会开始忽略它们。只对真正需要人工干预的情况发告警,其他的用自动化处理。
灰度发布与回滚
Loop的Prompt和逻辑变更属于高风险操作。一个看似微小的Prompt修改可能导致输出质量的大幅波动。灰度发布是控制这种风险的核心手段。
7.1 灰度策略
按流量比例灰度:最简单的方式。比如先将1%的流量切到新版本,观察指标无异常后逐步提升到5%、20%、50%、100%。
按用户群体灰度:先对内部用户或特定群体开放新版本,收集反馈后再扩大范围。
按地域灰度:先在低风险的地域上线,确认无问题后再推广到所有地域。
`python
class CanaryRouter:
def __init__(self, canary_ratio=0.01):
self.canary_ratio = canary_ratio
self.canary_version = None
self.stable_version = None
def route(self, request):
# 基于请求ID的确定性路由,同一用户总是走同一版本
hash_val = hash(request.user_id) % 1000
if hash_val < self.canary_ratio * 1000:
return self.canary_version
return self.stable_version
def promote_canary(self, new_ratio):
“””提升灰度比例”””
self.canary_ratio = min(1.0, new_ratio)
def rollback(self):
“””回滚到稳定版本”””
self.canary_version = self.stable_version
self.canary_ratio = 0
`
7.2 自动回滚
自动回滚是灰度发布安全网的关键组件。当新版本的关键指标出现异常时,系统应该自动回滚到上一个稳定版本。
自动回滚的触发条件:
- 错误率突增(比基线高50%以上)
- 延迟突增(P95比基线高100%以上)
- 质量评分突降(低于基线20%以上)
- 成本突增(比基线高200%以上)
`python
class AutoRollback:
def __init__(self, baseline_metrics, thresholds):
self.baseline = baseline_metrics
self.thresholds = thresholds
def check_and_rollback(self, current_metrics, router):
reasons = []
if current_metrics.error_rate > self.baseline.error_rate * (1 + self.thresholds.error_rate):
reasons.append(f”错误率异常: {current_metrics.error_rate:.2%}”)
if current_metrics.p95_latency > self.baseline.p95_latency * (1 + self.thresholds.latency):
reasons.append(f”延迟异常: {current_metrics.p95_latency:.0f}ms”)
if reasons:
router.rollback()
self.alert(reasons)
return True
return False
`
7.3 Prompt版本管理
Prompt是Loop的核心资产。生产环境需要像管理代码一样管理Prompt。
版本化:每个Prompt都有版本号,支持查看历史版本和对比差异。
A/B测试:同时运行两个版本的Prompt,通过统计显著性检验确定哪个更好。
回滚能力:发现问题时,能一键回滚到上一个稳定版本。
`python
class PromptRegistry:
def __init__(self):
self.prompts = {} # name -> {version -> prompt_content}
self.active = {} # name -> version
def register(self, name, version, content, metadata=None):
if name not in self.prompts:
self.prompts[name] = {}
self.prompts[name][version] = {
“content”: content,
“metadata”: metadata or {},
“created_at”: time.time()
}
def activate(self, name, version):
self.active[name] = version
def get_active(self, name):
version = self.active.get(name)
return self.prompts[name][version] if version else None
def rollback(self, name):
“””回滚到上一个版本”””
current = self.active.get(name)
if not current:
return False
versions = sorted(self.prompts[name].keys())
idx = versions.index(current)
if idx > 0:
self.active[name] = versions[idx – 1]
return True
return False
`
实战:部署一个生产级Loop
让我们把前面讨论的所有原则整合到一个完整的生产级Loop实现中。
8.1 架构设计
`
┌─────────────┐
│ API Gateway │
│ (限流/认证) │
└──────┬──────┘
│
┌──────▼──────┐
│ 调度器 │
│ (优先级队列) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
│ Worker 1 │ │Worker2│ │ Worker N │
│ ┌────────┐ │ │ │ │ ┌────────┐ │
│ │ Loop │ │ │ … │ │ │ Loop │ │
│ │ Engine │ │ │ │ │ │ Engine │ │
│ └────────┘ │ │ │ │ └────────┘ │
└─────┬──────┘ └───┬───┘ └─────┬──────┘
│ │ │
└────────────┼────────────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
│ LLM API │ │ Cache │ │ 工具服务 │
└───────────┘ └───────┘ └───────────┘
`
8.2 核心实现
`python
import asyncio
import time
import uuid
from dataclasses import dataclass, field
from typing import Optional, Dict, Any
from enum import Enum
class LoopState(Enum):
QUEUED = “queued”
RUNNING = “running”
COMPLETED = “completed”
FAILED = “failed”
TIMEOUT = “timeout”
BUDGET_EXHAUSTED = “budget_exhausted”
CANCELLED = “cancelled”
@dataclass
class LoopConfig:
max_iterations: int = 10
max_tokens: int = 50000
total_timeout: float = 600 # 秒
iteration_timeout: float = 120 # 秒
call_timeout: float = 60 # 秒
min_quality: float = 0.8
min_improvement: float = 0.02
model: str = “gpt-4o”
fallback_model: str = “gpt-3.5-turbo”
@dataclass
class LoopResult:
task_id: str
state: LoopState
output: Optional[str] = None
iterations: int = 0
total_tokens: int = 0
total_cost: float = 0
duration_ms: float = 0
model_used: str = “”
error: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
class ProductionLoop:
def __init__(self, config: LoopConfig, llm_client, cache, metrics):
self.config = config
self.llm = llm_client
self.cache = cache
self.metrics = metrics
async def execute(self, task_id: str, task_input: str) -> LoopResult:
start_time = time.time()
state = LoopState.RUNNING
try:
# 检查缓存
cached = await self.cache.get(task_input)
if cached:
self.metrics.record_cache_hit(task_id)
return LoopResult(
task_id=task_id,
state=LoopState.COMPLETED,
output=cached,
iterations=0,
total_tokens=0,
duration_ms=(time.time() – start_time) * 1000
)
# 执行Loop
result = await asyncio.wait_for(
self._run_loop(task_id, task_input),
timeout=self.config.total_timeout
)
# 缓存结果
await self.cache.put(task_input, result.output)
return result
except asyncio.TimeoutError:
state = LoopState.TIMEOUT
return LoopResult(
task_id=task_id, state=state,
error=”Loop执行超时”,
duration_ms=(time.time() – start_time) * 1000
)
except BudgetExhausted as e:
state = LoopState.BUDGET_EXHAUSTED
return LoopResult(
task_id=task_id, state=state,
error=str(e),
duration_ms=(time.time() – start_time) * 1000
)
except Exception as e:
state = LoopState.FAILED
return LoopResult(
task_id=task_id, state=state,
error=str(e),
duration_ms=(time.time() – start_time) * 1000
)
async def _run_loop(self, task_id, task_input):
context = [{“role”: “user”, “content”: task_input}]
total_tokens = 0
prev_quality = 0
for iteration in range(self.config.max_iterations):
self.metrics.record_iteration(task_id, iteration)
# 调用LLM
response = await self._call_with_retry(context)
total_tokens += response.usage.total_tokens
# 更新上下文
context.append({“role”: “assistant”, “content”: response.content})
# 评估质量
quality = self._evaluate_quality(response.content, task_input)
self.metrics.record_quality(task_id, iteration, quality)
self.metrics.record_tokens(task_id, iteration, response.usage.total_tokens)
# 检查终止条件
if quality >= self.config.min_quality:
return LoopResult(
task_id=task_id,
state=LoopState.COMPLETED,
output=response.content,
iterations=iteration + 1,
total_tokens=total_tokens,
model_used=self.config.model
)
if quality – prev_quality 0:
return LoopResult(
task_id=task_id,
state=LoopState.COMPLETED,
output=response.content,
iterations=iteration + 1,
total_tokens=total_tokens,
model_used=self.config.model,
metadata={“reason”: “improvement_plateau”}
)
prev_quality = quality
# 检查Token预算
if total_tokens > self.config.max_tokens * 0.9:
raise BudgetExhausted(“Token预算即将耗尽”)
# 生成改进反馈
feedback = self._generate_feedback(response.content, quality)
context.append({“role”: “user”, “content”: feedback})
# 达到最大迭代次数
return LoopResult(
task_id=task_id,
state=LoopState.COMPLETED,
output=context[-1][“content”],
iterations=self.config.max_iterations,
total_tokens=total_tokens,
model_used=self.config.model,
metadata={“reason”: “max_iterations_reached”}
)
async def _call_with_retry(self, context, max_retries=3):
for attempt in range(max_retries):
try:
return await asyncio.wait_for(
self.llm.chat(context, model=self.config.model),
timeout=self.config.call_timeout
)
except RateLimitError:
wait_time = 2 ** attempt
await asyncio.sleep(wait_time)
except APIError as e:
if attempt == max_retries – 1:
# 最后一次重试,尝试降级模型
return await self.llm.chat(context, model=self.config.fallback_model)
await asyncio.sleep(2 ** attempt)
raise APIError(“LLM调用失败,重试次数已用尽”)
def _evaluate_quality(self, output, task_input):
# 简化实现:实际中可能需要另一个LLM调用来评估
# 或者基于规则的检查
if not output or len(output) < 10:
return 0.1
# 更多评估逻辑…
return 0.6 # 默认中等质量
def _generate_feedback(self, output, quality):
return f”请改进你的回答。当前质量评分:{quality:.2f}。请更准确、更完整地回答问题。”
`
8.3 部署清单
将以上代码部署到生产环境,需要确保以下各项:
基础设施:
- [ ] 消息队列部署(Redis/RabbitMQ)
- [ ] Worker进程自动重启机制
- [ ] 健康检查端点
- [ ] 日志收集系统
- [ ] 指标监控系统(Prometheus/Grafana)
安全:
- [ ] API Key安全管理(使用环境变量或密钥管理服务)
- [ ] 请求认证和授权
- [ ] 输入验证和消毒
- [ ] 输出安全过滤
成本控制:
- [ ] Token预算设置
- [ ] 模型路由配置
- [ ] 缓存策略配置
- [ ] 成本告警阈值设置
可靠性:
- [ ] 超时配置
- [ ] 重试策略配置
- [ ] 降级策略配置
- [ ] 熔断器配置
可观测性:
- [ ] 结构化日志
- [ ] 分布式追踪
- [ ] 关键指标仪表盘
- [ ] 告警规则配置
发布:
- [ ] 灰度发布配置
- [ ] 自动回滚规则
- [ ] Prompt版本管理
- [ ] 回滚演练
常见陷阱
陷阱一:过度自信的初始部署
很多团队在本地测试通过后就直接全量上线。这通常会导致灾难。
正确做法:始终从灰度开始,逐步放量,密切监控。
陷阱二:忽略上下文膨胀
Loop的上下文随迭代累积,但很多开发者没有设置上下文窗口的上限。当上下文超出模型限制时,调用会失败。
正确做法:监控上下文长度,在接近限制时压缩历史或截断旧内容。
陷阱三:没有降级策略
只考虑了”正常路径”,没有考虑LLM API不可用时怎么办。
正确做法:始终准备降级方案——可以是更简单的模型、缓存结果、甚至是预设的默认回复。
陷阱四:日志不够详细
出问题时才发现日志信息不足,无法定位根因。
正确做法:在设计阶段就确定日志格式和关键字段。宁可多记一些,事后可以减少。
陷阱五:忽视长尾延迟
平均延迟看起来不错,但P99延迟可能高得离谱。
正确做法:同时监控P50、P95、P99延迟。为长尾请求设置更激进的超时。
陷阱六:Prompt变更不走流程
直接在线上修改Prompt,没有灰度和回滚机制。
正确做法:Prompt变更应该像代码变更一样走CR、灰度、监控、回滚流程。
陷阱七:成本不可见
直到月底账单出来才发现成本远超预期。
正确做法:实时监控成本,设置日/周预算告警。在Loop设计阶段就估算单次成本。
陷阱八:重试风暴
当API出问题时,所有Loop同时开始重试,形成”重试风暴”,进一步加重API负担。
正确做法:使用指数退避加随机抖动(jitter)。在重试前检查是否值得重试。
`python
async def retry_with_backoff(func, max_retries=3, base_delay=1.0):
for attempt in range(max_retries):
try:
return await func()
except RetryableError:
if attempt == max_retries – 1:
raise
# 指数退避 + 随机抖动
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
await asyncio.sleep(delay)
`
陷阱九:过度优化
在系统上线初期就投入大量精力做性能优化,而忽略了功能迭代和用户反馈。
正确做法:先确保功能正确,收集真实数据后再有针对性地优化。过早优化是万恶之源。
陷阱十:忽视安全
AI应用的安全威胁比传统应用更复杂——Prompt注入、数据泄露、模型滥用都是真实风险。
正确做法:在系统设计阶段就考虑安全,包括输入验证、输出过滤、访问控制、审计日志。
总结
生产环境Loop的工程是一个系统性工程,涉及调度、并发、成本、监控、发布等多个维度。让我们回顾核心要点:
延迟管理:通过自适应调度控制迭代次数,通过模型路由选择合适的模型,通过缓存避免重复计算。记住:延迟是用户体验的第一杀手。
成本控制:通过Token预算设置上限,通过模型路由降低单位成本,通过缓存减少重复消耗。成本控制应该是系统设计的核心约束,而不是事后优化。
可靠性保障:通过重试和降级应对API不稳定,通过超时和取消防止资源泄漏,通过熔断器防止级联故障。记住:在AI系统中,失败是常态,优雅处理失败才是能力。
并发管理:通过背压机制保护系统稳定性,通过队列解耦请求和处理,通过弹性伸缩应对流量波动。不要让突发流量压垮你的系统。
可观测性:通过结构化日志和分布式追踪了解系统内部状态,通过关键指标监控系统健康,通过告警及时发现问题。你无法优化你看不到的东西。
灰度发布:通过灰度策略控制变更风险,通过自动回滚快速止血,通过Prompt版本管理保障可追溯性。Prompt变更应该像代码变更一样严肃对待。
最后,记住一个重要的心态转变:生产环境不是开发环境的放大版,而是一个全新的工程领域。在开发环境中,你的目标是”跑通”;在生产环境中,你的目标是”在不确定性中提供确定性的服务”。这需要完全不同的思维方式和工程实践。
本篇提供了生产Loop的核心原则和实践模式。但纸上得来终觉浅——最好的学习方式是把一个Loop部署到真实环境,在真实的挑战中积累经验。从小规模开始,逐步完善,边做边学。
在下一篇,也就是系列的最后一篇中,我们将把Harness Engineering和Loop Engineering两个系列的所有概念串起来,从零构建一个完整的AI代码审查Agent。
下一篇预告:第六篇:Loop + Harness 完整实战 —— 把Harness Engineering和Loop Engineering两个系列的所有概念串起来,从零构建一个完整的AI代码审查Agent。
发表回复