AI PRO·Loop Day 5 生产环境Loop

作者:

系列导航第一篇:什么是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。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注