Skip to content

推理循环指南

其他语言

目录


概述

推理循环是 Symbiont 中自主智能体的核心执行引擎。它通过一个结构化的循环驱动 LLM、策略门控和外部工具之间的多轮对话:

  1. Observe(观察) — 收集上一次工具执行的结果
  2. Reason(推理) — LLM 生成提议的动作(工具调用或文本响应)
  3. Gate(门控) — 策略引擎评估每个提议的动作
  4. Act(执行) — 已批准的动作被分发给工具执行器

循环将持续运行,直到 LLM 生成最终文本响应、达到迭代/token 限制或超时。

设计原则

  • 编译时安全:通过 Rust 的类型系统在编译时捕获无效的阶段转换
  • 渐进式复杂度:循环只需提供者和策略门控即可工作;知识桥接、Cedar 策略和人机协作都是可选的
  • 向后兼容:添加新功能(如知识桥接)永远不会破坏现有代码
  • 可观测性:每个阶段都会发出日志事件和追踪 span

快速开始

最小示例

use std::sync::Arc;
use symbi_runtime::reasoning::circuit_breaker::CircuitBreakerRegistry;
use symbi_runtime::reasoning::context_manager::DefaultContextManager;
use symbi_runtime::reasoning::conversation::{Conversation, ConversationMessage};
use symbi_runtime::reasoning::executor::DefaultActionExecutor;
use symbi_runtime::reasoning::loop_types::{BufferedJournal, LoopConfig};
use symbi_runtime::reasoning::policy_bridge::DefaultPolicyGate;
use symbi_runtime::reasoning::reasoning_loop::ReasoningLoopRunner;
use symbi_runtime::types::AgentId;

// Set up the runner with default components
let runner = ReasoningLoopRunner {
    provider: Arc::new(my_inference_provider),
    policy_gate: Arc::new(DefaultPolicyGate::permissive()),
    executor: Arc::new(DefaultActionExecutor::default()),
    context_manager: Arc::new(DefaultContextManager::default()),
    circuit_breakers: Arc::new(CircuitBreakerRegistry::default()),
    journal: Arc::new(BufferedJournal::new(1000)),
    knowledge_bridge: None,
};

// Build a conversation
let mut conv = Conversation::with_system("You are a helpful assistant.");
conv.push(ConversationMessage::user("What is 6 * 7?"));

// Run the loop
let result = runner.run(AgentId::new(), conv, LoopConfig::default()).await;

println!("Output: {}", result.output);
println!("Iterations: {}", result.iterations);
println!("Tokens used: {}", result.total_usage.total_tokens);

使用工具定义

use symbi_runtime::reasoning::inference::ToolDefinition;

let config = LoopConfig {
    max_iterations: 10,
    tool_definitions: vec![
        ToolDefinition {
            name: "web_search".into(),
            description: "Search the web for information".into(),
            parameters: serde_json::json!({
                "type": "object",
                "properties": {
                    "query": { "type": "string" }
                },
                "required": ["query"]
            }),
        },
    ],
    ..Default::default()
};

let result = runner.run(agent_id, conv, config).await;

阶段系统

类型状态模式

循环使用 Rust 的类型系统在编译时强制执行有效的阶段转换。每个阶段是一个零大小类型标记:

pub struct Reasoning;      // LLM produces proposed actions
pub struct PolicyCheck;    // Each action evaluated by the gate
pub struct ToolDispatching; // Approved actions executed
pub struct Observing;      // Results collected for next iteration

AgentLoop<Phase> 结构体携带循环状态,并且只能调用与其当前阶段适配的方法。例如,AgentLoop<Reasoning> 只暴露 produce_output(),该方法消耗 self 并返回 AgentLoop<PolicyCheck>

这意味着以下错误是编译错误,而不是运行时 bug: - 跳过策略检查 - 在推理之前分发工具 - 在分发之前观察结果

阶段流程

graph TD
    R["AgentLoop<Reasoning>\nproduce_output()"]
    P["AgentLoop<PolicyCheck>\ncheck_policy()"]
    T["AgentLoop<ToolDispatching>\ndispatch_tools()"]
    O["AgentLoop<Observing>\nobserve_results()"]
    LR["LoopResult"]

    R --> P
    P --> T
    T --> O
    O -->|Continue| R
    O -->|Complete| LR

推理提供者

InferenceProvider trait 对 LLM 后端进行抽象:

#[async_trait]
pub trait InferenceProvider: Send + Sync {
    async fn complete(
        &self,
        conversation: &Conversation,
        options: &InferenceOptions,
    ) -> Result<InferenceResponse, InferenceError>;

    fn provider_name(&self) -> &str;
    fn default_model(&self) -> &str;
    fn supports_native_tools(&self) -> bool;
    fn supports_structured_output(&self) -> bool;
}

云端提供者 (OpenRouter)

CloudInferenceProvider 连接到 OpenRouter(或任何 OpenAI 兼容端点):

export OPENROUTER_API_KEY="sk-or-..."
export OPENROUTER_MODEL="google/gemini-2.0-flash-001"  # optional
use symbi_runtime::reasoning::providers::cloud::CloudInferenceProvider;

let provider = CloudInferenceProvider::from_env()
    .expect("OPENROUTER_API_KEY must be set");

策略门控

每个提议的动作在执行前都会通过策略门控:

#[async_trait]
pub trait ReasoningPolicyGate: Send + Sync {
    async fn evaluate_action(
        &self,
        agent_id: &AgentId,
        action: &ProposedAction,
        state: &LoopState,
    ) -> LoopDecision;
}

pub enum LoopDecision {
    Allow,
    Deny { reason: String },
    Modify { modified_action: Box<ProposedAction>, reason: String },
}

内置门控

  • DefaultPolicyGate::permissive() — 允许所有动作(开发/测试用)
  • DefaultPolicyGate::new() — 默认策略规则
  • OpaPolicyGateBridge — 桥接到基于 OPA 的策略引擎
  • CedarGate — Cedar 策略语言集成

策略拒绝反馈

当动作被拒绝时,拒绝原因会作为策略反馈观察反馈给 LLM,允许它在下一次迭代中调整策略。


动作执行

ActionExecutor Trait

#[async_trait]
pub trait ActionExecutor: Send + Sync {
    async fn execute_actions(
        &self,
        actions: &[ProposedAction],
        config: &LoopConfig,
        circuit_breakers: &CircuitBreakerRegistry,
    ) -> Vec<Observation>;
}

内置执行器

执行器 描述
DefaultActionExecutor 带有每个工具超时的并行分发
EnforcedActionExecutor 通过 ToolInvocationEnforcer 委托到 MCP 管线
KnowledgeAwareExecutor 拦截知识工具,将其余委托给内部执行器

断路器

每个工具都有一个关联的断路器来跟踪故障:

  • Closed(关闭)(正常):工具调用正常进行
  • Open(打开)(触发):连续故障太多;调用立即被拒绝
  • Half-open(半开)(探测):允许有限的调用来测试恢复
let circuit_breakers = CircuitBreakerRegistry::new(CircuitBreakerConfig {
    failure_threshold: 3,
    recovery_timeout: Duration::from_secs(60),
    half_open_max_calls: 1,
});

知识-推理桥接

KnowledgeBridge 将智能体的知识存储(分层记忆、知识库、向量搜索)连接到推理循环。

设置

use symbi_runtime::reasoning::knowledge_bridge::{KnowledgeBridge, KnowledgeConfig};

let bridge = Arc::new(KnowledgeBridge::new(
    context_manager.clone(),  // Arc<dyn context::ContextManager>
    KnowledgeConfig {
        max_context_items: 5,
        relevance_threshold: 0.3,
        auto_persist: true,
    },
));

let runner = ReasoningLoopRunner {
    // ... other fields ...
    knowledge_bridge: Some(bridge),
};

工作原理

在每个推理步骤之前: 1. 从最近的用户/工具消息中提取搜索词 2. query_context()search_knowledge() 检索相关项 3. 结果被格式化并注入为系统消息(替换之前的注入)

在工具分发期间: KnowledgeAwareExecutor 拦截两个特殊工具:

  • recall_knowledge — 搜索知识库并返回格式化结果

    { "query": "capital of France", "limit": 5 }
    

  • store_knowledge — 将新事实存储为主语-谓语-宾语三元组

    { "subject": "Earth", "predicate": "has", "object": "one moon", "confidence": 0.95 }
    

所有其他工具调用将不变地委托给内部执行器。

循环完成后: 如果启用了 auto_persist,桥接将提取助手响应并将其存储为工作记忆,供未来对话使用。

向后兼容性

knowledge_bridge 设置为 None 使运行器的行为与之前完全相同——没有上下文注入、没有知识工具、没有持久化。


对话管理

Conversation 类型

Conversation 管理有序的消息序列,支持序列化为 OpenAI 和 Anthropic API 格式:

let mut conv = Conversation::with_system("You are a helpful assistant.");
conv.push(ConversationMessage::user("Hello"));
conv.push(ConversationMessage::assistant("Hi there!"));

// Serialize for API calls
let openai_msgs = conv.to_openai_messages();
let (system, anthropic_msgs) = conv.to_anthropic_messages();

Token 预算强制

循环内的 ContextManager(不要与知识 ContextManager 混淆)管理对话的 token 预算:

  • 滑动窗口:优先删除最旧的消息
  • 观察掩码:隐藏冗长的工具结果
  • 锚定摘要:保留系统消息 + N 条最近消息

持久化日志

每个阶段转换都会向配置的 JournalWriter 发出一个 JournalEntry

pub struct JournalEntry {
    pub sequence: u64,
    pub timestamp: DateTime<Utc>,
    pub agent_id: AgentId,
    pub iteration: u32,
    pub event: LoopEvent,
}

pub enum LoopEvent {
    Started { agent_id, config },
    ReasoningComplete { iteration, actions, usage },
    PolicyEvaluated { iteration, action_count, denied_count },
    ToolsDispatched { iteration, tool_count, duration },
    ObservationsCollected { iteration, observation_count },
    Terminated { reason, iterations, total_usage, duration },
    RecoveryTriggered { iteration, tool_name, strategy, error },
}

默认的 BufferedJournal 将条目存储在内存中。生产环境部署可以实现 JournalWriter 以进行持久化存储。


配置

LoopConfig

pub struct LoopConfig {
    pub max_iterations: u32,        // Default: 25
    pub max_total_tokens: u32,      // Default: 100,000
    pub timeout: Duration,          // Default: 5 minutes
    pub default_recovery: RecoveryStrategy,
    pub tool_timeout: Duration,     // Default: 30 seconds
    pub max_concurrent_tools: usize, // Default: 5
    pub context_token_budget: usize, // Default: 32,000
    pub tool_definitions: Vec<ToolDefinition>,
}

恢复策略

当工具执行失败时,循环可以应用不同的恢复策略:

策略 描述
Retry 使用指数退避重试
Fallback 尝试替代工具
CachedResult 如果缓存足够新则使用缓存结果
LlmRecovery 让 LLM 寻找替代方案
Escalate 路由到人工操作队列
DeadLetter 放弃并记录失败

测试

单元测试(不需要 API 密钥)

cargo test -j2 -p symbi-runtime --lib -- reasoning::knowledge

使用 Mock 提供者的集成测试

cargo test -j2 -p symbi-runtime --test knowledge_reasoning_tests

使用真实 LLM 的实时测试

OPENROUTER_API_KEY="sk-or-..." OPENROUTER_MODEL="google/gemini-2.0-flash-001" \
  cargo test -j2 -p symbi-runtime --features http-input --test reasoning_live_tests -- --nocapture

实现阶段

推理循环分五个阶段构建,每个阶段添加新功能:

阶段 重点 关键组件
1 核心循环 conversationinferencephasesreasoning_loop
2 弹性 circuit_breakerexecutorcontext_managerpolicy_bridge
3 DSL 集成 human_criticpipeline_config、REPL 内置命令
4 多智能体 agent_registrycritic_auditsaga
5 可观测性 cedar_gatejournalmetricsschedulertracing_spans
Bridge 知识 knowledge_bridgeknowledge_executor
orga-adaptive 高级 tool_profileprogress_trackerpre_hydrate、扩展 knowledge_bridge

高级原语 (orga-adaptive)

orga-adaptive 特性门控添加了四项高级功能。详见完整指南

原语 用途
Tool Profile 基于 glob 的 LLM 可见工具过滤
Progress Tracker 带有卡住循环检测的每步重试限制
Pre-Hydration 从任务输入引用进行确定性上下文预获取
Scoped Conventions 通过 recall_knowledge 进行目录感知的约定检索
let config = LoopConfig {
    tool_profile: Some(ToolProfile::include_only(&["search_*", "file_*"])),
    pre_hydration: Some(PreHydrationConfig::default()),
    ..Default::default()
};

下一步