CHAPTER 01
功能概述
分层解耦的桌面智能体架构,让基础设施、业务逻辑与 AI 推理各司其职
架构定位
Tazhiyou 是一款基于 C++ 与 Qt6 构建的桌面智能助手。其架构遵循"基础设施 → 业务模型 → AI 集成 → 视图交互 → 控制编排"的五目录分层设计,通过 ServiceLocator 依赖注入容器 管理组件生命周期,以 IService / IRepository / IModule 三套接口契约约束跨层协作,最终由 AppController 统一编排 7 个功能模块。AI 能力由 Agent 循环驱动,首轮检索知识库(RAG),后续轮次调用工具完成多步推理。
设计目标
- ·关注点分离:基础设施与业务逻辑解耦,UI 与 AI 推理解耦,每层可独立演进
- ·接口契约:三套抽象接口(服务/仓储/模块)约束跨层协作边界,编译期类型安全
- ·依赖反转:高层模块不直接依赖低层实现,统一通过容器注入,支持测试替换
- ·本地优先:默认 Ollama 本地推理,离线可用;在线模型作为可选增强
核心能力
- 推理Agent 循环多轮工具调用,最大 5 轮,首轮 RAG 注入知识上下文
- 知识KnowledgePipeline 7 阶段流水线,维基百科获取到 Embedding 检索
- 工具ToolRegistry 运行时动态注册,技能清单注入系统提示
- 编排AppController 统一注册/初始化/关闭 7 个功能模块
CHAPTER 02
系统架构
五目录分层 · 三接口契约 · 依赖注入容器
源码五目录分层结构
五目录职责划分
三套接口契约
概念上形成三层契约关系:IRepository 约束数据访问层,IService 约束服务编排层,IModule 约束视图交互层。三者通过 ServiceLocator 容器解耦组装。
ServiceLocator 依赖注入容器
ServiceLocator 是核心的单例依赖注入容器,支持三种生命周期注册模式,通过 ServiceExtractor 类型擦除(shared_ptr<void> + if constexpr 编译期检测)实现跨层解耦,内部使用 QReadWriteLock 保证线程安全。
tryResolve<I>() → shared_ptr<I> // 宽松解析,未注册返回 nullptr
initializeAll() // 按注册顺序调用 IService::initialize
shutdownAll() // 逆序调用 IService::shutdown
CHAPTER 03
核心实现逻辑
Agent 循环 · RAG 检索增强 · 模块编排 · 模型管理
Agent 循环工作流程
Agent 循环关键参数
ModelManager 模型注册
ModelManager 管理 3 个 LLM 端点,默认使用本地 Ollama 推理,确保离线可用。在线模型(OpenAI/DeepSeek)作为可选增强。
openai_online → OpenAI · api.openai.com/v1 · gpt-4o-mini
deepseek → DeepSeek · api.deepseek.com/v1 · deepseek-chat
────────────────────────────
默认: m_currentId = "ollama_local"
maxTokens = 4096
AppController 模块编排
AppController 作为应用入口,持有 ModuleManager,在初始化阶段按顺序注册 7 个功能模块,每个模块实现 IModule 接口。注册后统一调用 initialize(),关闭时逆序调用 shutdown()。
CHAPTER 04
关键代码解析
从接口定义到循环调度的核心实现
片段 1 ServiceExtractor 类型擦除
// 类型擦除: shared_ptr<void> 存储 + if constexpr 编译期检测 IService using ServiceExtractor = std::function<std::shared_ptr<IService>(const std::shared_ptr<void>&)>; template<typename Interface> ServiceExtractor makeExtractor() { return [](const std::shared_ptr<void>& p) -> std::shared_ptr<IService> { // 编译期判断 Interface* 是否可隐式转换为 IService* if constexpr (std::is_convertible_v<Interface*, IService*>) { return std::static_pointer_cast<Interface>(p); } return nullptr; }; } // 容器内部以 shared_ptr<void> 统一存储, 提取时类型擦除还原 QHash<QString, std::shared_ptr<void>> m_instances; QReadWriteLock m_lock; // 读写锁保证线程安全
技术要点:使用 shared_ptr<void> 类型擦除存储异质实例,避免 dynamic_pointer_cast<IService> 的非法转换。通过 if constexpr 在编译期判断接口是否继承 IService,类型安全且零运行时开销。
片段 2 Agent 循环调度
// Agent 循环最大轮次 static constexpr int kMaxAgentIterations = 5; void startAgentLoop(const QString& userQuery) { // 首轮 RAG: 召回知识库 Top3 相关条目 auto results = KnowledgeBase::instance().search(userQuery, 3); // 构建系统提示: 知识上下文 + 已启用技能清单 QString systemPrompt = buildSystemPrompt(results); // 保留上下文轮数 const int maxRounds = qMax(10, AppConfig::contextRounds()); continueAgentLoop(0); // 进入第一轮 } void continueAgentLoop(int iteration) { if (iteration >= kMaxAgentIterations) { emit reply("已达到最大推理轮次限制"); return; } // 非流式调用 (stream=false), 超时 60s m_model->chatWithTools(m_messages, /*timeout=*/60000); // QNetworkReply::finished 信号回调 onToolReply } void onToolReply(const QJsonArray& toolCalls) { if (toolCalls.isEmpty()) { emit reply(finalText); // 无工具调用, 结束 } else { executeToolCalls(toolCalls, iteration); // 危险操作二次确认 continueAgentLoop(iteration + 1); // 下一轮 } }
设计要点:首轮注入 RAG 检索结果与技能清单,使 LLM 感知可用知识与能力;非流式(stream=false)简化工具调用解析;工具结果以 role=Tool 消息回传,LLM 在下一轮自主分析结果决定是否继续调用或给出最终回复。5 轮上限防止死循环。
片段 3 IService 生命周期接口
class IService { public: virtual ~IService() = default; // 服务名称 (用于日志与诊断) virtual QString serviceName() const = 0; // 初始化: 加载资源/建立连接/注册子组件 virtual Result<void> initialize() = 0; // 关闭: 释放资源/断开连接/持久化状态 virtual Result<void> shutdown() = 0; // 就绪状态查询 virtual bool isReady() const = 0; }; // ServiceLocator.initializeAll() 按注册顺序调用 initialize() // ServiceLocator.shutdownAll() 逆序调用 shutdown() (后注册先关闭)
契约意义:所有注册到容器的服务必须实现 IService,确保生命周期可控。逆序关闭保证依赖关系——被依赖的服务后关闭,避免悬空引用。
片段 4 IRepository 泛型仓储
template<typename TEntity, typename TKey> class IRepository { public: virtual Result<TKey> save(const TEntity& entity) = 0; virtual Result<TEntity> findById(const TKey& id) const = 0; virtual Result<QVector<TEntity>> find(const QueryOptions& opts = {}) = 0; virtual Result<void> remove(const TKey& id) = 0; virtual Result<int> count() = 0; virtual Result<bool> exists(const TKey& id) { /* 默认: findById */ } }; // QueryOptions: 分页 (limit/offset) + 过滤 + 排序 struct QueryOptions { int limit = -1; // -1 不限制 int offset = 0; QString orderBy; QString filter; };
领域实践:KnowledgeStore 实现 IRepository<KnowledgeEntry, qint64>,通过 QObject + IRepository 多继承(QObject 必须为第一基类)。findById() 为纯读操作,get() 才更新访问统计(hit_count)——两者职责分离。
CHAPTER 05
注意事项
架构设计中的关键约束与潜在陷阱
QObject 多继承顺序
实现 IRepository 时,QObject 必须为第一基类(Qt 元对象系统要求)。若顺序颠倒,moc 生成的元对象代码将无法正确工作,信号槽失效。KnowledgeStore 的正确写法:class KnowledgeStore : public QObject, public IRepository<KnowledgeEntry, qint64>。
dynamic_pointer_cast 禁忌
从 shared_ptr<void> 执行 dynamic_pointer_cast<IService> 是非法的——void* 不携带运行时类型信息(RTTI)。必须使用 ServiceExtractor 类型擦除方案,通过 if constexpr (std::is_convertible_v<Interface*, IService*>) 在编译期判断,再 static_pointer_cast 还原。
Agent 轮次与超时
Agent 循环硬上限为 5 轮(kMaxAgentIterations),防止 LLM 反复调用工具陷入死循环。chatWithTools 超时 60s,若 LLM 响应缓慢会触发超时中断。非流式(stream=false)意味着用户需等待完整响应,长文本生成时感知延迟较高——这是为简化工具调用解析做的取舍。
模块注册完整性
AppController 注册的 7 个模块需保持与视图层一致。FileConverterModule 模型层(converter/)完整存在但未注册到 AppController——这是已知的遗留缺口,意味着文件转换功能在视图层不可达。扩展新模块时务必同步更新 AppController 的注册列表。
关闭顺序依赖
shutdownAll() 逆序关闭——后注册的服务先关闭。这要求注册顺序遵循依赖方向:被依赖的基础服务(如 StructuredLogger、KnowledgeStore)先注册,依赖它们的业务服务后注册。违反此顺序会导致关闭时悬空引用。
本地模型默认策略
默认 m_currentId = "ollama_local",需用户本地运行 Ollama 服务(端口 11434)。若未启动,AI 功能不可用但应用其余模块不受影响。在线模型(OpenAI/DeepSeek)需配置 API Key,且存在网络延迟与隐私考量——架构上作为可选增强而非必需依赖。
CHAPTER 06
常见问题解答
架构原理层面的常见疑问与解答
桌面智能助手涉及 AI 推理、知识检索、系统控制等异质能力,传统 MVC 难以清晰划分。五目录(core/models/ai/views/app)按职责而非模式划分:core 提供基础设施,models 承载业务仓储,ai 独立 AI 集成,views 处理交互,app 统一编排。这种分层使各能力域可独立演进,且与依赖注入容器天然契合。
模板全特化要求所有服务类型在编译期已知,且容器需为每种类型生成独立存储,二进制膨胀。类型擦除(shared_ptr<void>)统一存储异质实例,运行时灵活注册;ServiceExtractor 配合 if constexpr 在编译期做类型安全检查,兼顾灵活性与零开销。代价是提取时需知道目标接口类型。
5 轮是经验阈值:绝大多数工具调用任务(查天气、读文件、执行命令)2-3 轮即可完成;复杂的多步推理(如"搜索知识→生成文档→导出")通常不超过 5 轮。过多的轮次往往意味着 LLM 陷入无效循环或任务定义不清。5 轮上限在功能完整性与资源消耗间取得平衡,同时 chatWithTools 的 60s 超时进一步约束单轮耗时。
Agent 循环需要解析完整的工具调用 JSON(tool_calls 数组),流式增量解析 JSON 复杂且易错——部分 JSON 片段无法合法解析。非流式(stream=false)等待完整响应后一次性解析,逻辑简洁可靠。代价是用户需等待完整响应,长文本生成时感知延迟较高。这是工程实用性的取舍。
三者职责正交:IService 约束服务生命周期(初始化/关闭),IRepository 约束数据访问(增删改查+分页),IModule 约束视图交互(元信息/创建视图)。一个组件可同时实现多个接口——例如 KnowledgeStore 实现 IRepository(数据访问)与 IService(生命周期);AIAssistantModule 实现 IModule(视图)与 IService(生命周期)。ServiceLocator 按 IService 维度统一管理初始化与关闭。
ErrorHandler 提供 guard<T>() 模板(try/catch + 自动上报)与 ErrorScope RAII(自动提取 Win32 系统错误),覆盖 9 个错误类别,500 条环形缓冲。StructuredLogger 提供 12 个 LogCategory 与 PerfTracer(纳秒级性能追踪)。两者均为 core 层基础设施,通过 ServiceLocator 单例注入,被所有上层模块共享——确保错误与性能数据全局可观测。