free-code-dotnet/docs/核心模块设计/核心模块设计-查询引擎-QueryEngine.md
应文浩wenhao.ying@xiaobao100.com e25ac591a7 init easy-code
2026-04-06 07:24:24 +08:00

11 KiB
Raw Permalink Blame History

核心模块设计 — 查询引擎 (QueryEngine)

所属项目: free-code .NET 10 重写 原始代码来源: ../../src/QueryEngine.ts 原始设计意图: 协调 LLM 消息流转、工具调用循环和 System Prompt 构建,是整个 Agent 行为的核心管道 上级文档: 核心模块设计总览 交叉参考: CLI 启动与解析 | 基础设施设计 — 状态管理


概述

QueryEngine 是 free-code 的核心消息处理管道。每当用户提交一条消息,IQueryEngine.SubmitMessageAsync 便接管控制权,驱动一个"助手回复 → 工具调用 → 执行结果 → 继续"的循环,直到助手不再请求工具调用或用户取消。

原始 QueryEngine.ts 是一个约 1200 行的 TypeScript 文件包含消息历史管理、System Prompt 构建、工具执行调度、上下文压缩判断、记忆提取等所有逻辑。.NET 重写将其中的 System Prompt 构建拆分为独立的 SystemPromptBuilder(实现 IPromptBuilder),其余核心循环保留在 QueryEngine 中。


6.1 IQueryEngine 接口

/// <summary>
/// LLM查询引擎 - 核心消息处理管道
/// 对应原始 ../../src/QueryEngine.ts
/// </summary>
public interface IQueryEngine
{
    /// <summary>提交用户消息并返回流式响应</summary>
    IAsyncEnumerable<SDKMessage> SubmitMessageAsync(
        string content,
        SubmitMessageOptions? options = null,
        CancellationToken ct = default);

    /// <summary>取消当前查询</summary>
    Task CancelAsync();

    /// <summary>获取消息历史</summary>
    IReadOnlyList<Message> GetMessages();

    /// <summary>获取当前token使用量</summary>
    TokenUsage GetCurrentUsage();
}

public record SubmitMessageOptions(
    string? Model = null,
    ToolPermissionContext? PermissionContext = null,
    string? QuerySource = null,
    bool IsSpeculation = false
);

IAsyncEnumerable<SDKMessage> 返回类型让调用方(终端 UI 层)可以在消息流抵达时即时渲染,无需等待整个响应完成。SDKMessage 是一个联合类型discriminated union涵盖流式文本增量、工具调用开始、工具执行结果等所有消息类型。


6.2 QueryEngine 实现

依赖关系

QueryEngine 通过构造函数注入以下依赖,每一项都有对应的原始 TypeScript 概念:

.NET 依赖 对应原始概念
IApiProviderRouter claude.ts 的 API 调用函数
IToolRegistry tools.tsgetTools()
IPermissionEngine permission.ts 的权限检查逻辑
IPromptBuilder getSystemPrompt() 内联函数
ISessionMemoryService memory.ts 相关逻辑
IFeatureFlagService isFeatureEnabled()

流式循环实现

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<QueryEngine> _logger;
    private readonly List<Message> _messages = new();
    private CancellationTokenSource? _activeCts;

    public async IAsyncEnumerable<SDKMessage> 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<Message> 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,便于测试和替换。

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<string> BuildAsync(
        IReadOnlyList<Message> 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($"<session_memory>\n{memory.Content}\n</session_memory>");

        // 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 对工具列表进行字典序排序(见工具系统)正是为了保证这一段的内容在相同工具集下保持不变。


取消机制

CancelAsync() 通过 CancellationTokenSource.Cancel() 触发取消。SubmitMessageAsync 内的 [EnumeratorCancellation] 特性确保当外部 CancellationToken 被取消时,异步枚举自动停止。

两个取消来源通过 CreateLinkedTokenSource 合并:

  • 用户调用 CancelAsync()(例如按 Ctrl+C
  • 外部传入的 ct 参数(例如超时或应用关闭)

参考资料