
AI 应用第一步是接一个大模型 API然后做一个聊天框。但我一直觉得这种方式离真实工作场景还差很远。在 FAE、技术支持、售前支持、嵌入式 SDK 支持这类岗位里真正困难的不是“让 AI 回答一句话”而是把客户零散、不完整、带情绪、带附件、带日志的问题整理成一个可以被定位、可以被回复、可以转给研发继续处理的问题流。所以我做了一个项目wechat_bot。它最初看起来像一个微信机器人但我后来把它扩展成了一个面向 FAE 技术支持流程的本地 AI Workbench。它不只是接入微信也不只是调用大模型而是把这些能力串成了一条完整链路客户消息 / 微信群 / 剪贴板 / 企业微信↓客户问题结构化↓本地资料库检索 Qdrant RAG 工程源码扫描↓SDK 版本关联 GitLab tag / branch / commit / Release Notes↓日志初筛 错误码提取 模块归属判断↓客户回复草稿 内部研发工单这套系统的价值不在于“自动回复”而在于把一个技术支持人员脑子里的经验、资料、判断逻辑和沟通方式工程化成一个可复用的 AI 工作台。一、为什么普通 AI 聊天不适合 FAE 场景真实客户问题通常不是这样的请问 ESPxxxx 在 SDK v2.3.1 下 OTA 失败的原因是什么而更可能是这样的客户某客户 型号ESPxxxx SDK v2.3.1固件 1.8.0 升级后无法联网日志里看到 WIFI_ERR_INIT_FAILED已附 boot.log 和截图。对人来说这句话背后其实包含很多隐含任务提取客户、型号、SDK、固件版本、现象、错误码判断属于 WiFi、Boot、OTA、BLE、低功耗还是驱动问题查公司内部资料、历史问题、PDF 手册、Excel 表格看 SDK 版本是否对应某个 GitLab tag 或 Release Notes从日志里找第一个关键错误点判断还缺哪些信息给客户写一版简洁回复给研发整理一版内部工单如果只是把这段话扔给大模型很容易得到一段看似完整但没有依据的回答。所以我在这个项目里坚持一个原则AI 不直接替我“编答案”而是先进入工程流程基于真实资料、真实日志、真实版本和真实工程上下文生成结果。二、整体架构设计这个项目分成几层。入口层 - local 手动模式 - clipboard 剪贴板模式 - wechat_pc 微信 PC - wecom 企业微信 核心处理层 - MessageBot - Router - SmartRouter - ConversationContext - KeywordLearner - StyleLearner - CorrectionLearner Web 工作台 - FastAPI backend - Next.js frontend AI 与知识层 - LLMClient - KnowledgeBase - DocumentLoader - RagStore - EmbeddingClient - ProjectContext - GitLabSdkResolver - LogAnalyzer - ResponseGenerator - TicketGenerator后端接口集中在 FastAPI Router 中例如router.post(/api/workbench/run, response_modelWorkbenchRunResponse) def run_workbench(req: WorkbenchRunRequest) - WorkbenchRunResponse: issue inbox.parse(...) knowledge kb.query(...) log_analysis log_analyzer.analyze(...) response response_generator.draft(...) ticket ticket_generator.generate(...) return WorkbenchRunResponse( issueissue, knowledgeknowledge, log_analysislog_analysis, responseresponse, ticketticket, )这就是整个系统最核心的设计不是单个 AI 问答而是一个 pipeline。客户输入进来后会依次经过问题结构化知识库检索日志分析客户回复生成研发工单生成这比单纯做聊天机器人更接近真实业务系统。三、资料库解析不只读 txt还要读 PDF、Word、Excel、源码FAE 场景里的资料非常杂。有 PDF 手册有 Word 报告有 Excel register map有 Markdown FAQ还有.c、.h、.py、.yaml、.log这些工程文件。所以我写了一个DocumentLoader统一把各种资料解析成 chunk。支持的文件类型包括TEXT_EXTENSIONS { .md, .txt, .log, .csv, .json, .yaml, .yml, .c, .h, .py } DOC_EXTENSIONS TEXT_EXTENSIONS | { .pdf, .docx, .xlsx }PDF 用pypdf提取文本Word 用python-docxExcel 用openpyxl。核心思路是不管原始文件是什么格式最后都变成统一结构dataclass(frozenTrue) class DocumentChunk: chunk_id: str path: Path title: str text: str line_start: int | None None line_end: int | None None这样后面的 RAG、关键词检索、资料引用、客户回复生成都不需要关心原始格式。我认为这一步很关键。很多 AI demo 只处理 Markdown 或纯文本但真实企业资料绝对不是这么干净的。一个能落地的 AI 工具必须能面对脏数据、旧文档、多格式资料和工程文件。四、RAG 检索Qdrant 向量检索 文件扫描兜底这个项目里我用了 Qdrant 做向量检索。索引过程大致是client.create_collection( collection_nameself.settings.qdrant_collection, vectors_configmodels.VectorParams( sizevector_size, distancemodels.Distance.COSINE, ), )每个 chunk 会被写入 Qdrantmodels.PointStruct( idstr(uuid.uuid5(uuid.NAMESPACE_URL, chunk.chunk_id)), vectorvector, payload{ chunk_id: chunk.chunk_id, path: str(chunk.path), title: chunk.title, line: chunk.line_start, snippet: chunk.text[:500], text: chunk.text, }, )搜索时先把问题转成 embedding再查 Qdrantquery_vector self.embeddings.embed_texts([query])[0] results client.search( collection_nameself.settings.qdrant_collection, query_vectorquery_vector, limittop_k, with_payloadTrue, )但我没有把系统设计成“Qdrant 挂了就不能用”。在KnowledgeBase.query()里我做了降级逻辑sources self.rag.search(query, req.top_k) retrieval_mode qdrant_vector if sources else file_scan if not sources: sources self.keyword_search(query, req.top_k)也就是说Qdrant 可用走向量检索Qdrant 不可用自动退回文件扫描本地资料少也可以直接跑后续交付再接正式 embedding API这个设计看起来简单但非常实用。真实工程里最怕的是 demo 时很炫一部署就因为某个服务没起来直接崩。我的思路是AI 系统也要有工程兜底能力。五、本地 embedding离线也能跑的确定性向量器很多 RAG 系统依赖外部 embedding API。但在企业内部资料、客户资料、FAE 支持场景里不是所有数据都适合直接传出去。所以我给系统设计了两种 embedding 模式property def mode(self) - str: if self.settings.embedding_mode api and self.settings.embedding_api_key: return api return local如果配置了 API就走外部 embedding。如果没有配置就走本地确定性 token vectorizer。本地模式会提取这些 tokenr0x[0-9a-fA-F]|[A-Za-z_][A-Za-z0-9_]{2,}|[\u4e00-\u9fff]{2,8}|\d\.\d(?:\.\d)?并对错误码、宏、函数名、十六进制值加权if _ in token or token.startswith(0x) or token.isupper(): weight 1.8这个设计非常适合嵌入式资料WIFI_ERR_INIT_FAILEDNVDSBLE_GAP0x53022044gpio_intr_callbackv2.3.1这些关键词在普通自然语言 embedding 里未必特别突出但在 FAE 场景里可能就是定位问题的关键。六、工程上下文扫描客户工程也参与回答很多客户问题最后发现不是 SDK bug而是工程配置、调用顺序或宏开关问题。所以我做了ProjectContext。用户可以输入本地客户工程路径系统会扫描PROJECT_EXTENSIONS { .c, .h, .cpp, .hpp, .py, .js, .ts, .tsx, .md, .txt, .log, .ini, .cfg, .json, .yaml, .xml, .cmake, .uvprojx }然后按 query 关键词匹配工程源码和配置文件。命中的结果会以project_scan的形式进入知识库回答retrieval_mode f{retrieval_mode}project_scan这让系统从“查资料”升级成了“结合资料和客户工程一起分析”。这点是我认为这个项目比较有价值的地方它不是泛泛的知识库问答而是在接近真实 FAE 排查流程。七、SDK 版本联动 GitLab把版本信息接进 AI 流程技术支持里经常会遇到一个问题客户说的 SDK 版本到底对应哪个 tag有没有 Release Notes有没有已知问题所以我写了GitLabSdkResolver。它会根据 SDK 版本尝试匹配GITLAB_REF_PATTERNS{version},v{plain},sdk-{version},release/{version}例如客户输入SDK v2.3.1系统会尝试v2.3.1 2.3.1 sdk-v2.3.1 sdk-2.3.1 release/v2.3.1 release/2.3.1如果匹配到 tag 或 branch就返回ref 类型commit shacommit titleweb urlrelease notes 片段然后作为gitlab_sdksource 加进知识库retrieval_mode f{retrieval_mode}gitlab_sdk这一步的意义是AI 不只是读文档还能理解“客户当前版本”对应的真实代码状态。八、日志分析正则规则 模块归属 知识库二次检索我没有直接让大模型看完整日志。因为日志分析第一步往往不需要大模型而是需要快速提取结构化线索。比如错误行匹配ERROR_LINE_RE re.compile( rerror|failed|fail|assert|panic|fault|exception|timeout|crc|invalid| r异常|失败|超时|复位|死机|错误|断连|无法, re.I, )模块归属规则MODULE_RULES [ (WiFi SDK, (wifi, wlan, dhcp, ssid, WIFI_)), (BLE SDK, (ble, gap, gatt, att, smp, bond, pair, 蓝牙)), (Boot/OTA, (boot, ota, crc, image, upgrade, flash, 升级)), (Power/LowPower, (sleep, wake, rtc, pwr, deepsleep, lightsleep, 唤醒, 低功耗)), ]日志分析结果包括关键错误行错误码模块归属时间线错误上下文初步假设缺失信息检查清单相关资料最有意思的是日志分析后还会再次调用知识库搜索related_docs self.kb.search(search_query, req.top_k_sources)这意味着日志不是孤立分析而是会和资料库联动。例如日志里出现ERROR WIFI_ERR_INIT_FAILED wifi init failed系统会判断偏向WiFi SDK再用型号、SDK 版本、错误码和模块信息去资料库里找相关文档。九、客户回复和研发工单分离很多 AI 系统最大的问题是输出对象不清楚。客户要的是简洁、明确、可执行。研发要的是环境、日志、复现路径、资料依据和待确认事项。所以我把输出分成两个生成器。客户回复class ResponseGenerator: def draft(self, req: ResponseDraftRequest) - ResponseDraft: ...要求是结构固定为已查到、建议先处理、还需补充。 总长度控制在 220 字以内。 必须基于给定资料/工程/日志依据。内部工单class TicketGenerator: def generate(self, req: TicketRequest) - InternalTicket: ...生成 Markdown 报告## 问题摘要 ## 客户环境 ## 关键日志 ## 相关资料 ## 初步判断 ## 已尝试步骤 ## 需要研发确认 ## 建议分配模块 ## 严重程度这就是我理解的 AI 工程化不是让 AI 输出一段万能文本而是根据不同工作对象生成不同格式的结果。十、一次完整调用示例请求curl -X POST http://127.0.0.1:8000/api/workbench/run ^ -H Content-Type: application/json ^ -d {\customer_text\:\客户某客户\n型号XC6517\nSDK v2.3.1固件 1.8.0\n升级后无法联网日志里看到 WIFI_ERR_INIT_FAILED已附 boot.log 和截图。\,\log_text\:\[00:00:01.112] boot start\n[00:00:03.410] sdk version v2.3.1\n[00:00:05.002] wifi init start\n[00:00:05.351] ERROR WIFI_ERR_INIT_FAILED wifi init failed\n[00:00:05.777] retry wifi init timeout\,\question\:\v2.3.1 升级后无法联网怎么排查\,\sdk_version\:\v2.3.1\,\attachments\:[\boot.log\,\screenshot.png\]}系统会返回{ issue: { product_model: XC6517, sdk_version: v2.3.1, firmware_version: 1.8.0, symptom: 升级后无法联网, error_keywords: [WIFI_ERR_INIT_FAILED], attachments: [boot.log, screenshot.png], priority: P1/P2, suggested_owner: WiFi SDK }, knowledge: { retrieval_mode: qdrant_vectorgitlab_sdk, sources: [] }, log_analysis: { module_owner: WiFi SDK, hypotheses: [ WiFi 初始化、配置迁移或连接参数不一致优先核对初始化顺序、SSID/加密方式和 DHCP 阶段日志。 ] }, response: { draft: 已查到该问题和 WiFi 初始化失败相关。建议先核对升级前后 WiFi 初始化顺序、旧配置迁移、SSID/加密方式和 DHCP 阶段日志。还需补充完整 boot log、升级前版本和复现步骤。 } }这类结果比“AI 给我解释一下 WiFi 初始化失败”更接近实际工作流。十一、为什么我认为这个项目有价值这个项目表面看是一个wechat_bot。但我自己更愿意把它定义成一个面向技术支持场景的个人 AI 基础设施。它包含了现在 AI 应用里非常热的几个方向RAG 知识库向量数据库 Qdrant多格式文档解析本地 embedding fallbackLLM 工作流编排FastAPI 后端服务Next.js Web 工作台GitLab DevOps 数据联动日志智能分析客户回复生成内部工单自动化Docker Compose 多服务部署更重要的是它不是为了炫技术而堆技术。每个模块都对应 FAE 工作里的真实问题。资料难找用 RAG。版本难对用 GitLab resolver。日志难看用 LogAnalyzer。客户回复慢用 ResponseGenerator。研发协作乱用 TicketGenerator。微信消息碎片化用 MessageBot 和多入口接入。这就是我做这个项目最想表达的东西真正有价值的 AI 应用不是把大模型放进聊天框而是把 AI 放进真实业务流程。十二、结语AI 时代个人能力也可以工程化做完这个项目后我最大的感受是AI 最适合放大那些已经有行业经验的人。如果一个人只会调用 API那很容易被替代。但如果一个人懂业务、懂工程、懂客户、懂研发协作还能把这些东西抽象成系统那他的价值会被 AI 放大。这个项目对我来说不只是一个工具。它更像是我把个人经验、资料体系、问题判断、客户沟通和研发协作方式沉淀成了一个可以持续进化的工作台。未来我会继续完善更强的 OCR 和扫描版 PDF 解析更高质量的 embedding 模型客户数据隔离和权限系统工单流转和审计历史问题自动学习团队级知识沉淀更完整的企业微信接入我相信未来每个技术团队都需要这样的 AI Workbench。而真正稀缺的人不是单纯会用 AI 的人而是能把 AI 和真实业务流程结合起来并持续做出结果的人。