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_model=WorkbenchRunResponse)
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(
        issue=issue,
        knowledge=knowledge,
        log_analysis=log_analysis,
        response=response,
        ticket=ticket,
    )

这就是整个系统最核心的设计:不是单个 AI 问答,而是一个 pipeline。

客户输入进来后,会依次经过:

  1. 问题结构化
  2. 知识库检索
  3. 日志分析
  4. 客户回复生成
  5. 研发工单生成

这比单纯做聊天机器人更接近真实业务系统。

三、资料库解析:不只读 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-docx,Excel 用 openpyxl

核心思路是:
不管原始文件是什么格式,最后都变成统一结构:

@dataclass(frozen=True)
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_name=self.settings.qdrant_collection,
    vectors_config=models.VectorParams(
        size=vector_size,
        distance=models.Distance.COSINE,
    ),
)

每个 chunk 会被写入 Qdrant:

models.PointStruct(
    id=str(uuid.uuid5(uuid.NAMESPACE_URL, chunk.chunk_id)),
    vector=vector,
    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,再查 Qdrant:

query_vector = self.embeddings.embed_texts([query])[0]

results = client.search(
    collection_name=self.settings.qdrant_collection,
    query_vector=query_vector,
    limit=top_k,
    with_payload=True,
)

但我没有把系统设计成“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。

本地模式会提取这些 token:

r"0x[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_FAILED
  • NVDS
  • BLE_GAP
  • 0x53022044
  • gpio_intr_callback
  • v2.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 sha
  • commit title
  • web url
  • release notes 片段

然后作为 gitlab_sdk source 加进知识库:

retrieval_mode = f"{retrieval_mode}+gitlab_sdk"

这一步的意义是:
AI 不只是读文档,还能理解“客户当前版本”对应的真实代码状态。

八、日志分析:正则规则 + 模块归属 + 知识库二次检索

我没有直接让大模型看完整日志。

因为日志分析第一步往往不需要大模型,而是需要快速提取结构化线索。

比如错误行匹配:

ERROR_LINE_RE = re.compile(
    r"error|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_vector+gitlab_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 fallback
  • LLM 工作流编排
  • 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 和真实业务流程结合起来,并持续做出结果的人。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐