# 核心模块设计 — 查询引擎 (QueryEngine) > 所属项目: free-code .NET 10 重写 > 原始代码来源: `../../src/QueryEngine.ts` > 原始设计意图: 协调 LLM 消息流转、工具调用循环和 System Prompt 构建,是整个 Agent 行为的核心管道 > 上级文档: [核心模块设计总览](核心模块设计.md) > 交叉参考: [CLI 启动与解析](核心模块设计-CLI启动与解析.md) | [基础设施设计 — 状态管理](../基础设施设计/基础设施设计-状态管理.md) --- ## 概述 `QueryEngine` 是 free-code 的核心消息处理管道。每当用户提交一条消息,`IQueryEngine.SubmitMessageAsync` 便接管控制权,驱动一个"助手回复 → 工具调用 → 执行结果 → 继续"的循环,直到助手不再请求工具调用或用户取消。 原始 `QueryEngine.ts` 是一个约 1200 行的 TypeScript 文件,包含消息历史管理、System Prompt 构建、工具执行调度、上下文压缩判断、记忆提取等所有逻辑。.NET 重写将其中的 System Prompt 构建拆分为独立的 `SystemPromptBuilder`(实现 `IPromptBuilder`),其余核心循环保留在 `QueryEngine` 中。 --- ## 6.1 IQueryEngine 接口 ```csharp /// /// LLM查询引擎 - 核心消息处理管道 /// 对应原始 ../../src/QueryEngine.ts /// public interface IQueryEngine { /// 提交用户消息并返回流式响应 IAsyncEnumerable SubmitMessageAsync( string content, SubmitMessageOptions? options = null, CancellationToken ct = default); /// 取消当前查询 Task CancelAsync(); /// 获取消息历史 IReadOnlyList GetMessages(); /// 获取当前token使用量 TokenUsage GetCurrentUsage(); } public record SubmitMessageOptions( string? Model = null, ToolPermissionContext? PermissionContext = null, string? QuerySource = null, bool IsSpeculation = false ); ``` `IAsyncEnumerable` 返回类型让调用方(终端 UI 层)可以在消息流抵达时即时渲染,无需等待整个响应完成。`SDKMessage` 是一个联合类型(discriminated union),涵盖流式文本增量、工具调用开始、工具执行结果等所有消息类型。 --- ## 6.2 QueryEngine 实现 ### 依赖关系 `QueryEngine` 通过构造函数注入以下依赖,每一项都有对应的原始 TypeScript 概念: | .NET 依赖 | 对应原始概念 | |-----------|-------------| | `IApiProviderRouter` | `claude.ts` 的 API 调用函数 | | `IToolRegistry` | `tools.ts` 的 `getTools()` | | `IPermissionEngine` | `permission.ts` 的权限检查逻辑 | | `IPromptBuilder` | `getSystemPrompt()` 内联函数 | | `ISessionMemoryService` | `memory.ts` 相关逻辑 | | `IFeatureFlagService` | `isFeatureEnabled()` | ### 流式循环实现 ```csharp public class QueryEngine : IQueryEngine { private readonly IApiProviderRouter _providerRouter; private readonly IToolRegistry _toolRegistry; private readonly IPermissionEngine _permissionEngine; private readonly IPromptBuilder _promptBuilder; private readonly ISessionMemoryService _memoryService; private readonly IFeatureFlagService _featureFlags; private readonly ILogger _logger; private readonly List _messages = new(); private CancellationTokenSource? _activeCts; public async IAsyncEnumerable SubmitMessageAsync( string content, SubmitMessageOptions? options = null, [EnumeratorCancellation] CancellationToken ct = default) { _activeCts = CancellationTokenSource.CreateLinkedTokenSource(ct); options ??= new SubmitMessageOptions(); // 1. 构建用户消息 var userMessage = new Message { Role = MessageRole.User, Content = new UserContent(content), Timestamp = DateTime.UtcNow, MessageId = Guid.NewGuid().ToString() }; _messages.Add(userMessage); yield return new SDKMessage.UserMessage(userMessage); // 2. 查询循环 (Assistant → ToolUse → Execute → Continue) var shouldContinue = true; while (shouldContinue && !_activeCts.Token.IsCancellationRequested) { // 2a. 构建System Prompt var systemPrompt = await _promptBuilder.BuildAsync( _messages, options.PermissionContext, options); // 2b. 获取可用工具 var tools = await _toolRegistry.GetToolsAsync(options.PermissionContext); // 2c. 调用API Provider var provider = _providerRouter.GetActiveProvider(); var apiRequest = new ApiRequest { SystemPrompt = systemPrompt, Messages = BuildApiMessages(_messages), Tools = tools, Model = options.Model }; await foreach (var response in provider.StreamAsync( apiRequest, _activeCts.Token)) { switch (response) { case SDKMessage.AssistantMessage am: _messages.Add(am.ToMessage()); yield return am; break; case SDKMessage.StreamingDelta sd: yield return sd; break; case SDKMessage.ToolUseStart tus: yield return tus; break; case SDKMessage.ToolUseResult tur: shouldContinue = tur.ShouldContinue; _messages.Add(tur.ToMessage()); yield return tur; break; case SDKMessage.CompactBoundary cb: yield return cb; break; } } } // 3. 后处理 await PostQueryProcessingAsync(options); } private async Task PostQueryProcessingAsync(SubmitMessageOptions options) { // 记忆提取 (EXTRACT_MEMORIES flag) if (_featureFlags.IsEnabled(FeatureFlags.ExtractMemories)) { _ = _memoryService.TryExtractAsync(_messages); // fire-and-forget } // Prompt建议 // 上下文压缩检查 // 统计更新 } public Task CancelAsync() { _activeCts?.Cancel(); return Task.CompletedTask; } public IReadOnlyList GetMessages() => _messages.AsReadOnly(); public TokenUsage GetCurrentUsage() => /* 从最近API响应头提取 */; } ``` ### 循环流程图 ``` 用户提交消息 │ ▼ 构建 System Prompt ◄────────────────────┐ │ │ ▼ │ 获取工具列表 │ │ │ ▼ │ 调用 API Provider (流式) │ │ │ ├── AssistantMessage → 存入历史 │ ├── StreamingDelta → 直接 yield │ ├── ToolUseStart → 直接 yield │ └── ToolUseResult ────────────────────┤ │ ShouldContinue = true │ └────────────────────────────►┘ │ ShouldContinue = false ▼ 后处理(记忆提取等) │ ▼ 结束 ``` --- ## 6.3 System Prompt Builder `SystemPromptBuilder` 对应原始 `QueryEngine.ts` 中内联的 `getSystemPrompt()` 函数,负责将多个信息源组装为最终发送给 API 的 System Prompt。 **原始设计意图:** 原始代码将系统指令、工具描述、命令描述、记忆、上下文信息拼接在一个函数中,每次查询都重新计算。.NET 重写将其提取为独立接口 `IPromptBuilder`,便于测试和替换。 ```csharp public class SystemPromptBuilder : IPromptBuilder { private readonly IToolRegistry _toolRegistry; private readonly ICommandRegistry _commandRegistry; private readonly ISessionMemoryService _memoryService; private readonly IFeatureFlagService _features; private readonly ICompanionService? _companionService; public async Task BuildAsync( IReadOnlyList messages, ToolPermissionContext? permissionContext, SubmitMessageOptions options) { var sb = new StringBuilder(); // 1. 基础系统指令 (CLAUDE.md 等价物) sb.AppendLine(await BuildBaseInstructionsAsync()); // 2. 工具描述 (JSON Schema) var tools = await _toolRegistry.GetToolsAsync(permissionContext); sb.AppendLine(BuildToolDescriptions(tools)); // 3. 命令描述 var commands = await _commandRegistry.GetEnabledCommandsAsync(); sb.AppendLine(BuildCommandDescriptions(commands)); // 4. 会话记忆 var memory = await _memoryService.GetCurrentMemoryAsync(); if (memory != null) sb.AppendLine($"\n{memory.Content}\n"); // 5. 上下文信息 (工作目录、Git状态、环境) sb.AppendLine(await BuildContextInfoAsync()); // 6. 同伴介绍 (BUDDY flag) if (_features.IsEnabled(FeatureFlags.Buddy) && _companionService != null) { var companion = await _companionService.GetCompanionAsync(); if (companion != null) sb.AppendLine(CompanionPromptBuilder.BuildIntro(companion)); } return sb.ToString(); } } ``` ### 六段构建逻辑说明 | 段落 | 内容来源 | 条件 | |------|----------|------| | 1. 基础指令 | `BuildBaseInstructionsAsync()` — 读取内置指令文本 | 始终包含 | | 2. 工具描述 | `IToolRegistry.GetToolsAsync()` — 当前可用工具的 JSON Schema | 始终包含 | | 3. 命令描述 | `ICommandRegistry.GetEnabledCommandsAsync()` — 斜杠命令列表 | 始终包含 | | 4. 会话记忆 | `ISessionMemoryService.GetCurrentMemoryAsync()` | 存在记忆时包含 | | 5. 上下文信息 | 工作目录、Git 状态、环境变量摘要 | 始终包含 | | 6. 同伴介绍 | `ICompanionService.GetCompanionAsync()` | `BUDDY` feature flag 开启时包含 | 工具描述(段落 2)的稳定性直接影响 Anthropic 的 prompt cache 命中率。`ToolRegistry` 对工具列表进行字典序排序(见[工具系统](核心模块设计-工具系统.md))正是为了保证这一段的内容在相同工具集下保持不变。 --- ## 取消机制 `CancelAsync()` 通过 `CancellationTokenSource.Cancel()` 触发取消。`SubmitMessageAsync` 内的 `[EnumeratorCancellation]` 特性确保当外部 `CancellationToken` 被取消时,异步枚举自动停止。 两个取消来源通过 `CreateLinkedTokenSource` 合并: - 用户调用 `CancelAsync()`(例如按 Ctrl+C) - 外部传入的 `ct` 参数(例如超时或应用关闭) --- ## 参考资料 - [核心模块设计总览](核心模块设计.md) - [CLI 启动与解析](核心模块设计-CLI启动与解析.md) - [工具系统](核心模块设计-工具系统.md) - [基础设施设计 — 状态管理](../基础设施设计/基础设施设计-状态管理.md) - [原始代码映射 — 核心模块](reference/原始代码映射-核心模块.md)