AIOps入门

2025-11-03T14:11:02+08:00 | 44分钟阅读 | 更新于 2025-11-03T14:11:02+08:00

@

学习目标

学完本章你应该能够:

  1. 用一句话讲清 Prompt(提示语) 与 Prompt Engineering 是什么,说明 token、上下文窗口(context window)如何决定你能喂给模型多少内容,并知道"提示语越长效果不一定越好"。
  2. 写出一个角色 + 上下文 + 输入/输出约束的 Prompt 模板,区分 zero-shot 与 few-shot,会用思维链(CoT)提升推理准确性,并掌握超长文档的分片 + 多次总结技巧,理解把知识库放进 system prompt 正是 RAG 的思想萌芽。
  3. 用 openai SDK 打出最简 Chat Completions 调用(system / user / assistant 三种角色),并基于"HTTP 无状态"理解如何通过回传历史消息实现多轮记忆;会用 LangChain 的 ConversationBufferMemory / ConversationBufferWindowMemory 管理记忆。
  4. 走通 JSON Mode:在请求里声明 response_format=json_object + system 提示含"json"关键字,让模型稳定输出可被程序消费的 JSON,并说清它"schema 固定、难以承载复杂逻辑"的局限。
  5. 讲清 Function Calling 的本质(模型只负责选函数并填充参数,真正调用由你代码执行),会用 tools + tool_choice 让模型从语义自动对接函数并获得参数化 JSON。
  6. 用 GPT(以 gpt-4o-mini / gpt-3.5 为例)微调一个"日志紧急程度分析专家":从 CSV 人工标注数据 → 转 JSONL → files.create 上传 → 创建微调任务 → 轮询状态 → 用 fine_tuned_model 推理,并知道微调四类使用场景与"数据质量是关键"。
  7. 走通 RAG 全流程(加载 → 分片 → embedding → 存向量库 → 检索 → 生成),理解"分片向量化模型与检索向量化模型必须一致",明白切分相似度、余弦相似度选择,并能说出 RAG"无法回答全局性/总结性问题"的缺陷。
  8. 理解 GraphRAG 如何通过知识图谱解决 RAG 不理解实体关系的痛点,会跑通 graphrag index/query 并用 Neo4j 可视化实体关系;知道 RAGFlow / QAnything 这类免写代码的开源工具。
  9. 用 Ollama 一行命令本地部署 Llama3.1 / Qwen / DeepSeek 并推理,会用 Modelfile 定制 system prompt,通过兼容 OpenAI 的 REST API 在代码里调用,并用 Open WebUI 提供类 ChatGPT 前端。

前置知识:有一点点 Python 基础(会用 pip 安装包、会运行 import 和函数调用即可);订阅一个可用的 OpenAI 兼容 API(官方或国内代理均可,测试阶段可用免费额度代理);会安装 Docker(用于 RAGFlow / Neo4j / Open WebUI 的容器化部署);对 Kubernetes、云原生、日志/可观测性有个模糊概念即可,本模块聚焦"如何把大模型用起来"。

本章你会动手做的事:

  • 用一段意图识别 Prompt 体会 zero-shot 与 few-shot 的区别,再测试给模型追加"让我们一步步思考"后推理质量的变化。
  • 用 openai SDK 先打一个最简 Chat Completions 请求,再升级成一个带多轮记忆的命令行对话(验证"你还记得刚才给我的命令吗")。
  • 用 LangChain 实现带 ConversationBufferMemory 的对话,体验一次语义参数提取的 JSON Mode,再把同样的"查日志/重启/部署"需求用 Function Calling(analyze_log)接管。
  • 用一份人工标注的 log.csv,走完 process_data.py 生成 log.jsonl → 上传 → 创建微调任务 → 用微调后模型做日志紧急度分类(P0/P1/P2)的完整链路。
  • 搭一个运维 Markdown 知识库,用 LangChain + ChromaDB 走通 RAG,验证"谁负责 payment 服务",再跑一遍 GraphRAG 问同样的问题看它能否回答"谁维护的服务最多"。
  • 用 Docker 一键拉起 RAGFlow / Open WebUI,上传同一份运维文档做基于文件的知识库问答。
  • 在本机用 ollama run 拉起 qwen2.5:7b / llama3.1:8b 并陪你对话,再用 Open WebUI 以类 ChatGPT 界面消费它。
AIOps入门Prompt 工程API·Memory·JSONFunction CallingFine-tuning 微调RAG·GraphRAGtoken·上下文窗口zero/few-shot·CoTChat Completions 接入记忆·JSON Modetools·tool_choice程序与模型闭环日志专家 P0/P1/P2JSONL·files·job知识库问答·Neo4jOllama·本地推理

第 1 讲:Prompt Engineering 入门与实战

AIOps 落地大模型,第一件要掌握的事就是把话说清楚。机器理解你的那句"帮我看一下这事咋回事",和大模型真正执行出来的质量,隔着整整一个 Prompt Engineering。

什么是 Prompt 与 Prompt Engineering

Prompt(提示语) 是给人工智能模型的输入文本或指令,用来引导模型生成特定的输出。Prompt Engineering(提示工程) 则是在使用大模型(如 GPT-4 系列)时,设计与优化输入的文本,以便获得期望输出的一门手艺。

一个核心结论值得先记住:一个好的 Prompt = 提出一个好的问题,或在上下文中包含一个好的答案。换句话说,输入端写了什么,几乎直接决定了输出端的质量。这正是我们花一章来学它的原因。

举个最常用的例子——用户意图识别:我们让大模型根据用户输入判断其意图,并在 Prompt 里给出三个参考例子,把每个例子对应的意图明确告诉它,最后再给它一段新的用户输入,让它对照上面三个例子给出判断。这样一段 Prompt 其实拆解出了四个组成要素:

要素作用在意图识别里的体现
目标让模型做角色扮演或实现特定功能“请你识别用户的意图”
示例给模型若干参考(few-shot)3 个输入→意图的对照例子
输入用户的真实输入或我们的数据那一段待判断的用户话术
输出我们期望模型的返回格式“请输出查询日志 / 查询指标 / 运维操作”

当你把这段 Prompt 编好后,就能直接把它发给大模型了。

Token、上下文窗口与模型推理基础

编写 Prompt 前,必须理解大模型"怎么看文本"。我们人类以字、词为单位理解语言,而大模型以 token 为单位。借助 OpenAI 官网提供的那类在线工具(Tokenizer playground / API 响应中的 usage),你随时可以统计一段 Prompt 对应多少个 token。

类比:大模型的上下文窗口就像你的一次性短期记忆——能同时记住的条数有限,塞得太多反而会忘掉重点。比人还不如的是,它是同一次请求里一次性喂进去的硬上限,一旦超了就截断或报错。

对应到工程里就是:每次调用大模型,能带进去的 输入 token + 输出 token 一定不能超过上下文窗口。你想塞多少内容、就要算清楚占多少 token,写 Prompt 时"够清楚即可",别贪多。

几个关键的机制要记住:

  • 商用大模型按 token 数量计费。
  • 大模型本质上是学习 token 之间的统计关系:它基于你输入的所有 token,计算"下一个 token"的概率分布,再挑选概率最高的那个作为输出——这就是大模型推理的最基本原理。
  • 所有输入(包括空格)都会占用 token。
  • 像人的短期记忆一样,大模型的输入 + 输出总量是有限的,这个上限就叫 上下文窗口(context window)。

不同模型的窗口限制不同,例如 GPT-4o 的上下文窗口约 128K、最大输出约 4096 token;GPT-4o mini 的窗口相对更大。窗口越大,你能输入的上下文就越多。

复习提示:踩坑:不要以为"上下文塞得越多,回答越越好"。模型的回答效果并不会随 token 数量线性增长,反而可能起反作用——无关信息一多,模型更容易被带偏。写到刚好描述清楚,比堆砌越厚越好。

编写优秀提示语的四个要点

把零散经验收敛成可复用的原则,Prompt Engineering 主要讲四点:

  1. 尽量给出具体的描述——多写"要做什么",少写"不要做什么"。告诉模型"直接给我一条命令"比"别给我写一长串解释"有效得多。
  2. 对重要的事进行排序——把你最希望优先完成的任务放在前面。
  3. 标准化格式输入/输出——输入可用 Markdown 语法组织,输出也可要求模型以 Markdown 或 JSON 输出。
  4. 拆解任务,甚至给出推理思路——把大任务拆成小步骤,并提示模型分步思考,能让推理更准确。

Zero-shot 与 Few-shot:给不给例子

从"给不给参考例子"这个维度,提示语分成两种重要模式:

  • zero-shot(零样本):只靠自然语言描述任务,不给任何具体例子。上面那句"通过用户输入识别意图,意图可能是查询日志 / 查询指标 / 运维操作"就属于 zero-shot,句子里没有任何示例。
  • few-shot(少样本):除了自然语言描述外,再给若干参考例子。比如意图识别里给出的三个"输入→意图"对照,就是 few-shot 的示例。

工程上更推荐多用 few-shot:有了示例之后,模型能更好地对齐你期望的输出风格与判断逻辑,生成往往更准确。

思维链(Chain of Thought,CoT)

CoT 的目的是让模型"别急着一口气给答案,先一步一步想,并在每一步给出解释"。这样做既能增强输出的可解释性,也能显著提升推理准确性——很像我们考试时一步一步推导,而不是靠感觉蒙答案。

CoT 对 zero-shot 与 few-shot 两种模式都有效。最简单的用法就是在提问后追加一句:"让我们一步一步思考,并且为每一步给出简单的解释。“只加这么一句话,就能触发模型的显式推理过程。

把知识库放进 System Prompt:RAG 的思想萌芽

在调用层面,大模型的消息通常分 system / user / assistant 三种角色,其中 system prompt(系统提示) 用于设定角色与全局规则。我们可以把内部专家知识库中与故障相关的部分直接放进 system prompt,让模型优先基于这份知识库回答,而不是依赖它训练时的记忆。

例如让模型扮演"运维专家”,把内部专家知识库文档里关于故障处理的内容放进 system prompt,再让它做故障分析与修复建议。这正是后续要讲的 RAG 的核心原理——把私有知识动态"喂"给模型。

超长文档怎么办:分片 + 总结

既然上下文窗口有限,那么一份远超窗口的运维文档如何让模型处理?答案是分片(chunking)并分步总结:

  1. 设一份超长文档含"工单格式"与"工单处理流程"两部分。
  2. 先分别总结"工单格式"、再总结"工单处理流程"。
  3. 再对两次总结的结果再次汇总总结。

类比:这就像一篇论文太长看不完——先把第一章总结成几行、第二章总结成几行,最后把两段"摘要"再合成一段摘要。你省了输入空间,但丢了部分细节。对应到工程里就是:用一部分准确性换取更多内容的输入,这是处理超长上下文的通用取舍。

flowchart TD
    D[超长文档] --> S1[分片总结
工单格式] D --> S2[分片总结
工单处理流程] S1 --> M[再次汇总总结
合并两份摘要] S2 --> M M --> R[带着摘要继续推理]

这张图在讲:一份远超上下文窗口的文档,通过"先分片各自总结、最后合并摘要",被压缩进有限的窗口里继续使用,是超长文档的处理精髓。

整个过程涉及三次推理。本质上是用一部分准确性,换取更多的内容输入——这是处理超长上下文的常见取舍。

程序辅助的 Prompt:让模型替你跑代码

大模型不仅能说话,还能输出程序。我们可以让模型直接输出代码,并在它内部的 sandbox(沙箱)环境里运行这段代码、拿到运行结果后再产出最终回答。这种方式非常适合数值计算、数学运算、数据分析、绘图这类任务——模型"算不准但会写代码",把计算外包给解释器往往更可靠。

第 2 讲:Chat Completions、Memory 与 JSON Mode 实战

这一讲开始进入真正的代码节奏。我们从"一行 Python 接上大模型"开始,逐步把它改造成"会记住上下文的对话",最后让模型输出稳定的 JSON 结构。

用 Chat Completions 接入大模型

接大模型的标准流程其实只有几步:装 Python → 安装 openai 包 → 申请 API key(测试阶段可用免费的国内代理,把 key 和 base_url 填进 client 即可)。

看官方文档里最简的例子,几句就够:

from openai import OpenAI

client = OpenAI()  # 默认读环境变量 OPENAI_API_KEY

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "你是一个运维专家,帮助用户解决技术问题。"},
        {"role": "user", "content": "Linux 端口被占用怎么排查?直接给我一条命令即可。"},
    ],
)
print(response.choices[0].message.content)

几个要点:

  • 如果你用的是国内代理,需要在 OpenAIClient 里额外配置 api_key 与 base_url,指向代理的对外开放地址。
  • 不要硬编码 API key,通过环境变量(OPENAI_API_KEY)提供更安全、也更利于换环境复用。
  • 真正的大模型输出在 response.choices[0].message.content 字段;其他字段都是响应的元信息。

这里构造 messages 时用到了三种角色,贯穿始终:

角色含义
system系统提示,设定角色与全局规则
user用户输入
assistant模型上一次的输出 / 推理结果

Memory:如何让大模型记住对话

新的问题出现了:模型回答完,我怎样才能基于它的回答继续追问?比如它给了我一串 Linux 命令,我怀疑命令不对,该怎么接着往下聊?

关键认知是:Chat Completions 的每一次推理,本质上就是发起一次 HTTP 请求,而 HTTP 请求是无状态的——对模型而言,每次推理都是一次独立的推理。想让它"记得"刚才的内容,思路非常简单:把上一次模型的 assistant 输出,在下次推理时当作历史消息带回 messages,再附一条新的 user 消息即可。

类比:HTTP 无状态下的"记忆"很像两个人写信——你每次寄信都得把之前的往来内容重新抄一遍附进去,对方才能把前后连起来看。对应到工程里就是:对话本身不保存状态,记忆完全靠"每次都把历史 messages 原样带回"来维持;少带一条,模型就"失忆"一次。

sequenceDiagram
    participant U as 程序/用户
    participant M as 大模型
    U->>M: 第1轮 system + user(端口被占用怎么排查?)
    M-->>U: assistant(lsof -i :端口)
    U->>M: 第2轮 system + 第1轮user + assistant + 新user(命令忘了,再给我)
    M-->>U: 基于历史再答(记忆生效)

这张图在讲:因为每次推理都是独立无状态的 HTTP 请求,多轮记忆靠的是把上一轮 assistant 输出也塞回 messages;代码里每轮 messages.append(...) 逐步累积历史即可。

手写一个带记忆的对话,核心结构如下:

messages = [{"role": "system", "content": "你是一个运维专家。"}]

while True:
    user_input = input("请输入你的问题:")      # 例如"Linux 端口被占用怎么排查?"
    messages.append({"role": "user", "content": user_input})

    response = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
    reply = response.choices[0].message.content
    print("运维专家的输出:", reply)

    messages.append({"role": "assistant", "content": reply})   # 回写模型输出,形成记忆

一轮循环后,messages 里会包含三条消息:system、user、assistant,这就是一个完整的对话上下文。你可以用"我之前给你的命令是什么?“来验证模型是否真的记住了刚才的内容——它能准确说出 lsof ... 这类答案,说明多轮记忆生效了。

用 LangChain 更好地管理记忆

手写记忆虽然能跑,但"该记多少、窗口装不装得下"都要自己操心,很麻烦。这时可以引入 LangChain,它封装了更灵活的记忆管理。LangChain 主要提供四类长期记忆手段:

方式作用
ConversationBufferMemory最简单的记忆,通过传递全部历史消息实现
ConversationBufferWindowMemory只记住最后 N 轮对话(设 k 值)
限制 token 数防对话 token 超出模型上限,比如只记住 128K 以内
对超限对话做总结在有限窗口下记住更多内容(但要额外推理,费用更高)

用 ConversationBufferMemory 实现我们刚才手写的对话,代码非常接近:

from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

llm = ChatOpenAI(
    model="gpt-4o-mini",
    openai_api_key="...",      # 或统一走环境变量 / base_url
    openai_api_base="...",
)
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

conversation.run(user_input)   # 内部会自动把上下文写入 memory

需要装两个包并固定版本:langchain==0.2.8(教材对应版本)与 langchain_openai 等配套组件。

复习提示:注意:ConversationBufferMemory 的底层逻辑跟手写的几乎一样——通过传递历史消息实现记忆。程序退出后上下文不会自动保存。若想跨会话保留,需要你把自己的用户消息与 AI 消息持久化到数据库,下次对话时再读回来拼进 messages。

JSON Mode:让模型输出稳定的 JSON

传统的应用是 前端 → 后端 → 数据库,用户在前端的所有操作都以 JSON 为载体和后端交互。到了大模型时代,可以把大模型看成"新型前端界面”——但前提是它能输出稳定的结构(JSON 或 schema)供程序解析。让大模型稳定输出 JSON 的技术,就叫 JSON Mode(JSON 模式)。

启用 JSON Mode 有两个前置条件:

  1. 在请求里声明格式约束:response_format={"type": "json_object"}。
  2. system 提示里必须包含 “json” 关键字,让模型知道自己当前在处理 JSON 业务。
response = client.chat.completions.create(
    model="gpt-4o-mini",
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": (
                "你是一个 JSON 对象提取专家。参考我的 JSON 定义输出 JSON 对象,"
                "字段 service_name 是服务名称,action 只能取 get_log / restart_service / delete_workload。"
            ),
        },
        {"role": "user", "content": "帮我重启 payment 服务"},
    ],
)

模型会按你定义好的 JSON schema 做字段填充,例如返回:

{"service_name": "payment", "action": "restart_service"}

使用场景:JSON Mode 主要用于从语义化的内容里提取参数,比如意图识别、程序拿到 JSON 后做下一步处理;配合 few-shot(多给几个例子)能获得更稳定的输出。

复习提示:局限:JSON Mode 的 schema 是写死的——service_name、action 这些都必须先写进 system 提示。一旦需求变多、定义复杂,这种方式就不够灵活,也很难承载复杂业务逻辑。生产上要实现更复杂的编排,需要转向下一讲的 Function Calling。

第 3 讲:Function Calling 实战

先回顾 JSON Mode 遇到的新需求:希望大模型同时处理"查最近两天 payment 服务日志、重启 payment 服务、部署服务"等多种情况并返回对应 JSON。

你会发现——每种需求的 JSON 定义都不一样。想用 JSON Mode 实现,system 提示就得分别写"查日志时的 JSON 格式"“重启时的 JSON 格式"“部署时的 JSON 格式”,整个提示会搬得又长又复杂;更关键的是,语义化的需求千变万化,你根本无法枚举所有情况、也无法用自然语言描述动态的 schema。这就是 JSON Mode 的根本局限。

Function Calling(函数调用) 正是为解决这个问题而生。

Function Calling 的核心思想

Function Calling 让大模型间接调用你代码里的函数方法,并自动帮你填充函数所需参数,从而实现了非常灵活的调用机制。还是上面对话例子:

  • 我在对话前提前定义好三个函数的列表(tools):get_log(获取日志)、restart_service(重启服务)、apply(部署/发布)。
  • 把这些函数的作用告诉模型(description)。
  • 用户说"我要查找最近两天 payment 服务的日志”→ 模型选择调用 get_log,并自动填充参数 service=payment、time_range=最近两天。
  • 用户说"重启 payment 服务"→ 模型同样自动填充参数。

于是,你想扩展一个新功能时,只需要扩展 tools 列表,完全不用改 system prompt——这样就能实现非常灵活的函数化调用。

定义 Tools:name + description + parameters

要使用 Function Calling,首先在代码里定义函数列表 tools,并把它们传进 Chat Completions 接口。tools 是一个数组,支持传 N 个函数;每个函数都带 description(说明它是干什么的)。以一个简单的 analyze_log 为例:

tools = [
    {
        "type": "function",
        "function": {
            "name": "analyze_log",
            "description": "根据查询字符串(lucene 语法)从日志系统里检索日志并分析错误原因",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "lucene 查询字符串,例如 app=graph AND keyword包含 error",
                    }
                },
                "required": ["query"],
            },
        },
    }
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "你是日志分析助手,可以帮助用户分析日志、调用多个函数完成任务,并分析错误产生的可能原因。"},
        {"role": "user", "content": "我要查看 app=graph 且关键词包含 error 的日志"},
    ],
    tools=tools,
    tool_choice="auto",
)

print(response.choices[0].message.tool_calls)

关键点:

  • tools 支持传 N 个函数;每个函数要写清 name、description、parameters(参数对象,含各参数类型与描述)与 required(必填参数)。
  • tool_choice="auto" 让模型自动选择最合适的函数。

运行后模型并不会直接给出最终答案,而是返回一个 tool_calls:包含想调用的函数名 analyze_log 以及填充好的参数:

模型想调用的函数是:analyze_log
参数 arguments:{"query": "app=graph AND error"}

你把用户输入里的 service 从 graph 换成 payment、error 换成 failed,模型同样会自动把这些语义参数填进 analyze_log 的 query。

一个关键认知:真正调用的是你的代码

Function Calling 并不是让大模型直接调用你的方法——大模型只会输出字符串,它既拿不到你的函数,也执行不了任何副作用。真正"调用函数"这个动作,必须由你自己的代码来做:拿到模型返回的 tool_calls 后,你在代码里根据函数名去调用对应的 Python 函数,并把参数值传进去。

类比:这像在餐厅点单——你只负责把"想吃什么"描述清楚给服务员(模型),具体哪个厨师做、怎么做,是后厨(你的代码)的事。对应到工程里就是:模型只负责"选哪个函数 + 填什么参数",真正执行函数副作用的一定是你自己的代码——模型输出的是 tool_calls 这份"点单",你拿着它去自己的代码里照做。

flowchart LR
    subgraph R1[第一轮推理]
        U[用户: 查 app=graph 的错误日志] --> M[(大模型)]
        M --> TC[tool_calls
analyze_log
query=app=graph AND error] end subgraph R2[你的代码] TC --> CALL[真实调用 analyze_log] CALL --> RES[返回日志内容] end RES --> M2[(大模型
第二轮推理)] M2 --> ANS[基于日志给出最终回答]

这张图在讲:Function Calling 是"模型选函数 → 代码真实执行 → 结果回填 → 二次推理"的闭环,模型与你的函数通过 tool_calls 和 tool 角色消息互相"对话"。

把上面简单的调用升级成一个"真实调用 + 二次推理"的闭环,结构大概是:

def analyze_log(query: str) -> str:
    # 真实的日志检索逻辑
    print("函数调用的参数是:", query)
    return "this is error log"   # 模拟检索到的示例日志

def run_conversation():
    # 1) 构造 messages 与 tools,发起第一轮
    response = client.chat.completions.create(model="gpt-4o-mini", messages=..., tools=tools, tool_choice="auto")
    tool_calls = response.choices[0].message.tool_calls

    # 2) 检查是否要求函数调用,若需要则真实调用并拿到结果
    available_functions = {"analyze_log": analyze_log}
    for call in tool_calls:
        function_name = call.function.name
        args = json.loads(call.function.arguments)
        result = available_functions[function_name](**args)
        # 3) 把函数返回结果以 tool 角色追加回 messages

    # 4) 基于函数真实返回的结果,再发一轮让模型做最终回答
    final = client.chat.completions.create(model="gpt-4o-mini", messages=updated_messages)
    return final.choices[0].message.content

运行后链路上的效果是:第一次推理让模型告诉我们"想调用 analyze_log、参数是 xxx"→ 代码真实调用该函数(打印"函数调用的参数是…“并返回 this is error log)→ 把函数返回值追加进 messages → 再做一次推理,这次模型已经"知道"查到了日志,会基于这段日志内容继续作答(如"我已查到关键词包含 error 且 app 为 graph 的日志,以下是一部分日志内容…,是否需要过滤?")。

复习提示:踩坑/补充:你可以发现,模型返回的 arguments 本身就是一段 JSON——是的,Function Calling 内部其实用 JSON Mode 打了底。此外,多个函数很简单:复制一份 tools 里的函数定义,补充到 available_functions,整个闭环即可跑通。想扩展"获取时间戳"“获取 Prometheus 某指标"等能力,照着加就行。

Function Calling 的用途

一句话:让 GPT 选择合适的函数完成任务,并从语义解析该函数所需参数,最终实现大模型与程序之间的连接。这是它最重要的价值——当你的推理过程需要依赖外部系统交互或外部工具才能完成时,就该考虑使用 Function Calling。它也是后续实现 ChatOps(通过聊天界面驱动运维自动化)这类流程的基础。

第 4 讲:Fine-tuning(微调)实战

Prompt Engineering、Function Calling 能让通用模型"会用”,但有些业务场景需要模型"有专业手感"——比如要让它判断一条日志的紧急程度。这时就该用 Fine-tuning(微调)。

为什么需要微调 & 四个使用场景

微调的目的:通过给大模型更多的样本数据来改进表现,让它在某些特定任务上输出更好。典型使用场景有四个:

  1. 要求模型输出特定的风格或格式;
  2. 提高模型输出的可靠性;
  3. 纠正模型无法遵守非常复杂的 system prompt 的情况;
  4. 让模型执行难以用语言表达清楚的新任务 / 新技能。

本讲的实战例子是:微调一个日志紧急程度分析专家,让它根据日志内容判断紧急等级,分类为 P0 / P1 / P2 三档。

准备微调数据:从人工标注到 JSONL

微调最重要的不是技术而是数据来源。数据可以从一份 Excel/CSV 出发,由你扮演运维专家,对一批日志逐条人工标注紧急等级——让模型"学习"你在标注每段日志时的内部逻辑,之后给任意一条日志,它都能像运维专家一样给出 P0/P1/P2。

但 Excel 文档不能直接用来微调,需要先处理成标准的微调数据格式(JSONL)。每条训练数据包含三个 message:

role内容
system例如"你是一个日志告警专家,请根据日志内容识别紧急度,直接输出 P0 / P1 / P2"
user日志内容本身
assistant期望输出(示例标签,如 P0)

一条微调数据大概是:

{"messages": [
    {"role": "system", "content": "你是一个日志告警专家,请根据日志内容识别紧急度,直接输出 P0、P1 或 P2。"},
    {"role": "user", "content": "disk usage 98%, probe timeout, replicas down..."},
    {"role": "assistant", "content": "P0"}
]}

有 N 条 Excel 记录,就转换成 N 条这样的 JSONL 数据(逐条对应,只是把格式重新组织)。转换逻辑用脚本完成:读 log.csv(两列:日志内容、优先级)→ 拼出上述 message 结构 → 输出 log.jsonl。

用 OpenAI API 走通微调链路

类比:把"微调"想成送新员工去岗前培训——你得先递交一份精心挑选的"员工档案"(人工标注数据),培训学校(云端)异步跑完后,发你一个专属编号的"毕业员工"(ft:... 模型)。对应到工程里就是:微调是异步任务,必须"上传数据 → 建任务 → 轮询状态 → 拿新模型 ID 推理"四步走,不会一次调用就同步完成。

flowchart LR
    CSV[人工标注 log.csv
日志 → P0/P1/P2] --> JL[脚本转 JSONL
system + user + assistant] JL --> UP[files.create 上传
purpose = fine-tune] UP --> JOB[fine_tuning.jobs.create
异步任务] JOB --> POLL[轮询 retrieve
running → succeeded] POLL --> FT[拿 fine_tuned_model
进行推理]

这张图在讲:微调数据先要从 Excel/CSV 转成 JSONL,再走"上传 → 建任务 → 轮询 → 拿新模型 ID 推理"的异步四步链路。

微调是异步任务,完整的调用链分为四步:

from openai import OpenAI
client = OpenAI()

# 1) 上传微调数据文件,purpose 必须标为 fine-tune
with open("log.jsonl", "rb") as f:
    file = client.files.create(file=f, purpose="fine-tune")
file_id = file.id
print("文件 ID:", file_id)

# 2) 基于该文件创建微调任务,指定基础模型
job = client.fine_tuning.jobs.create(
    training_file=file_id,
    model="gpt-4o-mini-2024-07-18",
)
job_id = job.id
print("微调任务 ID:", job_id)

# 3) 微调是异步的,轮询任务状态
status = client.fine_tuning.jobs.retrieve(job_id)
print("状态:", status.status)
print("微调后的模型 ID:", status.fine_tuned_model)

# 4) 微调完成后,用 fine_tuned_model 这个新模型 ID 直接做推理
resp = client.chat.completions.create(
    model=status.fine_tuned_model,   # 例如 ft:gpt-4o-mini-...:org::...:...
    messages=[
        {"role": "system", "content": "你是一个日志告警专家,请根据日志内容识别紧急度,直接输出 P0、P1 或 P2。"},
        {"role": "user", "content": "disk error, replica lost, permission denied"},
    ],
)
print(resp.choices[0].message.content)

执行后你会看到:刚刚提交任务时 fine_tuned_model 还为空、状态是"运行中(running)";等它完成后,retrieve 返回的 fine_tuned_model 就是一个形如 ft:gpt-4o-mini-... 的新模型 ID——它不是 gpt-4o-mini 那种公开模型名,而是专属于你的微调产物,直接用它做推理即可。

用这个微调模型测试时,system 提示要跟你造 JSONL 时的 system 保持一致。输入端给一条 disk error,它判为 P0;给一条"CPU 使用率过高",判为 P1——符合你的标注逻辑,微调就成功了。

复习提示:

几点补充:

  • 微调链路的完整顺序是:准备数据 → 上传文件 → 创建微调任务 → 获取微调后模型 ID → 用该 ID 做 chat。
  • 微调耗时取决于数据量大小,数据多的时候会比较慢,记得轮询状态而非死等。
  • 当前公开可微调的模型主要包括 gpt-4o-mini 与 gpt-3.5-turbo;gpt-4o 目前一般不允许公开微调(个别内测名额除外)。
  • 这套流程不是 OpenAI 专属:其他商用或开源模型(如火山引擎豆包)的微调方式与 JSONL 格式基本一致,整体是标准化流程。
  • 微调最大的难点永远是数据来源——需要大量高质量人工标注样本,模型才能学到你期望的内在逻辑。

第 5 讲:RAG 与 GraphRAG 实战

在讲 Prompt Engineering 时我们提过:可以把内部知识库当 system prompt 的一部分喂给模型。但受 token 限制,不可能无限把整份专家知识库塞进 system。一个自然的解法就此诞生:根据用户的问题,只把知识库里能回答该问题的相关片段"动态组装"进 system prompt。

这种"动态组装通识知识进 system"的方法,正是 RAG(检索增强生成,Retrieval-Augmented Generation) 的基础。一句话概括 RAG:把私有知识库引入大模型的完整流程。

类比:这像找一位图书馆管理员——你问"payment 服务谁维护?“他不会把整个图书馆背给你,而是去书架里翻出最相关的那几页递给你,你基于这几页作答。对应到工程里就是:RAG 不做全局学习,而是每次按问题去知识库"翻出"最相关的分片,再拼进 system prompt 让模型作答——既绕开 token 上限,又让模型回答长在你自己的私有知识上。

RAG 的两大步骤与索引三步

RAG 主要分两步:

阶段要解决什么动作
建立索引(索引阶段)搞清楚"哪段知识能回答哪个问题”分片(split)+ 向量化(embedding)+ 存储到向量库
检索与生成(推理阶段)把能回答用户提问的知识片提取出来向量近似度匹配 → 取相关块 → 放 system → 让模型作答

其中索引阶段又包含三个子步骤:

  1. 加载文档:把不同格式(txt / word / excel / PPT / PDF)的文档做文本化处理。借助 LangChain 的文档加载器,可以很轻松地把多种格式加载进来并转成纯文本。
  2. 拆分(分片):受上下文长度限制,按段落或指定分隔符把文档切成小块。策略有多种——按段落、按特殊分隔符(如 Markdown 的一级/二级标题)等。
  3. 向量化 + 存储:对分片做 embedding,把得到的向量存入向量数据库。

检索与生成阶段则借助 LangChain 的检索器(retriever),基于向量近似度匹配,从向量库找出与用户问题最相关的片段,取出来放进 system prompt,让大模型据此作答。整个过程可以用一张流程图串起来:

flowchart LR
    subgraph IDX[索引阶段(离线/异步)]
        A1[加载文档
txt/word/xlsx/ppt/pdf] --> A2[分片 split
按标题/段落切块] A2 --> A3[向量化 embedding
text-embedding-3-small] A3 --> A4[(向量数据库
ChromaDB)] end subgraph RT[检索与生成(在线/同步)] B1[用户问题] --> B2[向量化
同款 embedding 模型] B2 --> B3[近似度匹配
余弦相似度取 Top-K] A4 --> B3 B3 --> B4[取相关块文本
拼进 system prompt] B4 --> B5[大模型
基于知识库作答] end
复习提示:两条路径的模型一致性:左侧索引阶段的分片向量化模型,和右侧检索阶段给问题做向量化的模型,必须是同一款(例如都用 text-embedding-3-small)。否则向量维度都对不上,近似度匹配无法进行。

分片核心代码:按 Markdown 标题切分

本讲用一份运维专家知识库(一个 Markdown 文档)来做例子。它包含常见问题与解决方案(交易失败怎么办、响应时间过长怎么办、系统宕机怎么处理、安全问题等)以及不同服务的业务负责人。分片可以直接利用 Markdown 的标题语法(# / ## / ###)作为切分点:

from langchain.text_splitter import MarkdownHeaderTextSplitter

doc = open("data/ops-kb.md", encoding="utf-8").read()

headers_to_split_on = [
    ("#", "一级标题"),
    ("##", "二级标题"),
    ("###", "三级标题"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on, strip_headers=False)
chunks = splitter.split_text(doc)
print("分片数量:", len(chunks))     # 例如一共切出 11 片

每个分片都是一段独立文本,后续就可以分别做向量化了。

Embedding:向量化模型选择

向量化的代码非常简单——调用各厂商提供的 embedding(嵌入)模型即可,几乎每家商用大厂都提供对应模型,开源的也有。调用方式跟对话类似,只是模型不同、且不需要 messages:

from openai import OpenAI
client = OpenAI()

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input="payment 服务是谁维护的",
)
vector = resp.data[0].embedding
print("向量维度:", len(vector))   # text-embedding-3-small 默认输出 1536 维

关于 embedding 模型的选择:

  • OpenAI 兼容系列里,text-embedding-3-small(1536 维)与 text-embedding-3-large(维度更多)是比较新的模型。
  • 一般建议用 small:维度会决定向量存储的成本,embedding 本身也按量计费。对一个很大的文档做向量化时,成本是要综合考虑的;small 在性价比上通常已经够用。

向量检索:余弦相似度

有了所有分片的向量后,接下来就是把用户的提问和这些分片的向量做近似匹配。一般有两种算法:

  • 余弦相似度(cosine similarity)——推荐使用;
  • 欧几里得距离(Euclidean distance)。

计算结果越小代表越近似。匹配到 N 块后,通常取前几片(最近的若干块,注意取的是文本本身而非向量)放进 system 让模型基于知识库作答。

关键细节:分片索引阶段用的向量化模型,和检索阶段用的向量化模型必须保持一致。否则两者维度对不上,匹配会直接出问题。

操作上,一个精简的流程是:先对问题本身做 embedding → 用该向量去向量库做近似检索。用 LangChain + 本地向量库(ChromaDB)的实现示例:

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small", openai_api_key=..., openai_api_base=...)

# 把分片向量化并写入本地 ChromaDB(目录持久化)
vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, persist_directory="./chroma_dir")
vectorstore.persist()

# 文本相似性检索,带回相似度分数
results = vectorstore.similarity_search_with_score("payment 服务", k=1)
for doc, score in results:
    print(doc.page_content, score)          # 分数越小越接近

# 向量检索:先把问题 embedding 再查
query_vec = embeddings.embed_query("payment 服务是谁维护的")
results = vectorstore.similarity_search_by_vector(query_vec, k=1)

两条检索路径都能命中"payment 服务"最近似的块并给出相似度分值。

组装 RAG 检索链并生成答案

真正做 RAG 的时候,通常直接用 LangChain 提供的 retriever 组件 + 一个取自 LangChain Hub 的标准 RAG prompt 拉起来。这个 prompt 的长相是这样的:

你是一个问答助手(assistant)。请根据检索到的上下文(context)回答问题;如果不知道答案,就直接说"不知道",不要编造。

这条"不知道就说不知道“的指令,正是为了避免 RAG 幻觉——当某问题在上下文里根本匹配不到可回答的块时,模型宁可承认不知道,也不凭空捏造。

from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain

llm = ChatOpenAI(model="gpt-4o-mini", openai_api_key=..., openai_api_base=...)
retriever = vectorstore.as_retriever()

def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

qa_chain = (
    {"context": lambda _: format_docs(retriever.invoke("payment 服务由谁维护")), "question": lambda _: "payment 服务由谁维护"}
    | ...
)
result = qa_chain.invoke(...)

当你问”payment 服务是谁维护的“时,RAG 会把检索到的相关块(往往不止一块)拼进 context,交给模型;模型据此给出正确答案——“支付服务由小张维护,联系方式是……"。你还可以在链路里把"实际匹配到的文档内容"打出来,直观看到它选取了哪些知识块。

RAG 的两大短板

RAG 有明显短板,最突出的是两个:

  1. 分片是"暴力"截断的:文本分片时可能把上下文——本有逻辑关联的内容——拦腰截成 N 片,导致模型无法回答这些分片内部逻辑关联的问题。因为你取回的 N 块未必覆盖了全部内在关联,回答自然可能出错。
  2. 只能回答显式检索问题,不能回答全局性/总结性问题:比如问”谁维护的服务最多",RAG 无法回答。因为 RAG 本质是把你的问题做向量近似检索,谈不上语义理解与全局归纳。

你实际跑一下"谁维护的服务最多"会发现:它虽然也匹配到了一些文档块,但最终回答是”根据上下文,无法确定谁维护的服务最多"——这就是通常所说的 RAG 最大的缺陷:匹配不到正确的块。

GraphRAG:用知识图谱解决实体关系

RAG 检索依赖字面含义,不理解文档里对象之间的内在逻辑(实体关系)。微软因此提出了 GraphRAG(图检索增强生成):

  • 它能把知识库里的实体与关系提取出来,构建出知识图谱。
  • 检索时基于知识图谱,把具有关联性的对象与内容成簇提取出来给模型,从而回答全局性问题。
  • 不再简单依赖字面近似度,而是理解文档中对象间的内在关联(这些关联由大模型提取),完成查询。

这样就补齐了 RAG 的两大技术缺陷。

GraphRAG 实操:index 建立索引 + query 查询

实操上,GraphRAG 提供命令行工具。先安装并初始化工作区:

pip install graphrag
# 初始化 data 目录为 graphrag 工作区,生成 output / prompts / settings 等
python -m graphrag.index --init --root data

接着配置环境与参数:

  • 在 .env 里填两个环境变量:GRAPHRAG_API_KEY 与 GRAPHRAG_API_BASE(对应你使用的 API)。
  • 在 settings.yaml 里把模型改成 gpt-4o-mini,并把 embedding 相关的 base_url 也改成你的 API base,保证 embedding 和推理都能连通。

然后建立知识图谱索引——这一步会提取实体对象及它们之间的关系,产出到 output 目录(里面是一堆 parquet 图谱资源):

# 先把 Markdown 知识库放到 data/input/ 目录下(改成 .txt)
python -m graphrag.index --root data

最后做全局查询,用我们之前 RAG 回答不了的问题来验证:

python -m graphrag.query --root data --method global "谁负责/维护的服务最多?"

这一次 GraphRAG 直接给出答案:"小张负责的服务最多,因为他覆盖了支付操作的多方面,负责 payment 底层的多个服务(前端后端都在维护)。“它真正分析出了对象之间的实体关系,并把这种总结性问题回答上来了。相比 RAG,GraphRAG 能处理的场景宽得多;同时,字面含义类检索问题它也能正常处理。

用 Neo4j 可视化实体关系

GraphRAG 生成了一大堆实体关系资源,如果想直观查看,推荐用 Neo4j 图数据库可视化。流程是:

docker run -d -p 7474:7474 -p 7687:7687 neo4j:latest
# 浏览器访问 http://localhost:7474
# 用默认账号 neo4j 登录,修改密码(如 my_password)
  • 之后安装所需插件包,把 GraphRAG output 目录里创建的实体与关系导入 Neo4j(注意把代码里的目录名改成你实际输出的 output 目录)。
  • 导入后在 Neo4j Browser 里使用 Cypher 查询节点,就能看到每个实体对象在全文里产生的关系网络图。

免写代码的开源 RAG 工具:RAGFlow 与 QAnything

日常使用若不一定要自己开发一套全新 RAG 工具,可以直接上社区开源方案。两个代表性工具:

RAGFlow(InfiniFlow 开源) 可实现"上传知识库即问答"的能力:

  • 需要一台服务器/虚拟机(演示里用腾讯云虚拟机),用 Docker Compose 拉镜像启动(国内机器可用带 -cn 的 compose 文件,避开镜像拉取问题)。
  • 建议把 .env 里的版本指到较新的 release(如 V0.9.0),拉取的镜像会小不少。
  • 启动后浏览器访问对应端口,注册账号登录。
  • 在"模型供应商"里添加你注册的 OpenAI(填 base_url 与 key)。
  • 创建一个知识库(选中文、嵌入模型用 text-embedding-3-small、token 块大小可调到 500+),上传那份运维 Markdown 文件并启动 embedding。
  • 创建一个"智力助手 / 助理”(选模型如 GPT-4o,绑定刚建的知识库),即可直接对话。问"谁负责 payment 服务",它能成功检索出 Markdown 里的内容并正确回答。

QAnything(网易开源) 则强调"无需写任何代码"就能创建私有/个人知识库做 RAG。

两者特色对比:

工具特色短板
RAGFlow几乎支持所有常见文档类型、支持 OCR;匹配用"文字 + 向量"的混合排序—
QAnything有 rerank(重排) 设计,检索更准确对文档类型的支持稍差,用向量库做混合检索

两者按需选择即可。

第 6 讲:本地部署开源模型并推理

前几讲调用的是云端大模型,想换到本地部署开源模型,最好用的工具是 Ollama —— 它能用一行命令拉起一个开源大模型并完成本地推理。

Ollama:一条命令本地拉起模型

  • 安装:按官方文档下载,Windows / macOS / Linux 都支持。
  • 一条命令即可启动本地推理,命令由模型名 + 版本号拼接而成:
ollama run llama3.1:8b        # Meta Llama 3.1 8B
ollama run qwen2.5:7b         # 阿里通义千问 7B(中文效果好)

Ollama 支持 CPU 与 GPU 推理,官方模型库(library)里支持非常多的开源模型(Llama3.1、Google、Qwen、DeepSeek 等),在该列表里任选一个参数量,它就会给出对应的准确拉取命令。以 7B 的千问为例,在本地 Mac 上直接基于 CPU 推理(速度偏慢,但仍可用):

>>> 你是谁?
(千问回答)
>>> /bye      # 退出
复习提示:硬件门槛(CPU 推理时):7B 模型至少需要约 8G 内存,13B 至少约 16G,33B 需要约 32G。如果你的机型配置有限,请选能在本地跑得起来的参数量。

DeepSeek 系列模型也在此列。它擅长写代码,能力不错,相当于一个本地版 Copilot(写代码助手)——可以请它写 Python、Java 等代码,有效提升编码效率。

接入任意 GGUF 开源模型 & 定制 System Prompt

Ollama 不只支持官方 library 里的模型,还支持接入几乎任何开源模型,前提是 GGUF 格式。在 Hugging Face 上找到想要的 GGUF 模型 → 下载到本地 → 写一个 Modelfile 定义它 → ollama create 创建 → 运行:

FROM /path/to/local-model.gguf
ollama create my-custom-model -f Modelfile
ollama run my-custom-model

同理,若想定制 system prompt,也在 Modelfile 里指定:

FROM qwen2.5:7b
SYSTEM "你是这个本地 AIOps 助手的专属提示词……"
ollama create aiops-helper -f Modelfile
ollama run aiops-helper

类比:跑步谁都会,但健身房就在楼下才是真方便——随到随用、数据全在自己手里。对应到工程里就是:云端大模型是"外卖",本地 Ollama 是"自家厨房"——用一行命令跑起开源模型、数据不出本地,还能用兼容 OpenAI 的接口无缝接回你的代码。

flowchart LR
    LOCAL[(本地开源模型
qwen2.5 / llama3.1)] <--> O[Ollama
REST API 兼容 OpenAI] O --> CODE[openai SDK 代码
base_url = localhost:11434/v1] O --> UI[Open WebUI
类 ChatGPT 前端]

这张图在讲:Ollama 作为本地后端对外暴露兼容 OpenAI 的 REST API,既能被 openai SDK 的代码直接调用,也能被 Open WebUI 这类前端界面消费,所有推理都在本地完成。

用 OpenAI 兼容接口在代码里调用 Ollama

Ollama 提供 REST API,而且兼容 OpenAI 的接口,因此你可以用前面章节的 openai SDK 直接调用——只需要把 base_url 指向 Ollama、把 api_key 填一个占位值:

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",   # Ollama 默认端口
    api_key="ollama",                        # 占位,Ollama 不强校验
)
resp = client.chat.completions.create(
    model="qwen2.5:7b",
    messages=[{"role": "user", "content": "你好,你是谁?"}],
)
print(resp.choices[0].message.content)

这样你在代码里就有了一台"本地开源大模型",还能复用熟悉的那套接入方式。

Open WebUI:给 Ollama 配一个类 ChatGPT 前端

Ollama 本质上是后端,命令行交互式体验较差。推荐搭配 Open WebUI 作为前端工具化界面。安装同样简单,直接一条 Docker 命令即可启动,与 Ollama 无缝集成,界面非常接近 ChatGPT 前端:

docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main
  • Ollama 就在本机时,用你本机地址即可(也可以 --network=host);若 Ollama 部署在别的服务器,用相应地址即可。
  • 启动后访问 http://localhost:3000,注册账号登录,就能在界面里选中本地模型(如千问/llama)开始推理了。

Open WebUI 的能力还不止于对话:

  • 上传文件对话:直接把文件(如那份运维 Markdown)拖进去提问,它就能基于文件内容回答(含负责人、联系方式等)——甚至不需要像 RAGFlow 那样做复杂的 embedding 配置。
  • 引用 URL 对话:对话里直接贴一个网址,即可基于该网页内容进行总结/问答。
  • 搜索引擎:可配置 Google 等搜索。
  • Ollama 多实例负载均衡:生产环境把模型部署到不同 GPU 实例,通过负载均衡提升输出 TPS(承载能力)。
  • TTS / STT:支持语音识别(语音提问)与文本转语音(朗读回答)。
  • 集成 ComfyUI:实现文生图能力。
  • 多模型对话:支持同时与多个模型对比对话。
复习提示:注意 RAGFlow 与 Open WebUI 的定位差异:RAGFlow 的侧重点是支持更多文档类型;而 Open WebUI 对文件类型的解析能力较一般——它通常只能较好处理 txt、markdown、PDF 这类简单文本,PPT、Excel 等复杂文件可能解析不准确。

总结

这一章从"怎么把话对大模型说清楚"一路走到了"在本地把开源模型跑起来",覆盖了 AIOps 应用开发中最常用的一整套大模型技术栈。

  • Prompt Engineering:理解 token 与上下文窗口,掌握 zero-shot / few-shot / CoT,学会把知识库动态组装进 system prompt,并处理超长文档的分片与多次总结。
  • Chat Completions / Memory / JSON Mode:最简 API 接入、三种消息角色、基于"HTTP 无状态"实现多轮记忆,用 LangChain 的 memory 家族管理上下文,用 JSON Mode 拿到稳定 JSON 结构。
  • Function Calling:定义 tools,模型负责"选函数 + 填参数",你的代码负责"真实调用 + 二次推理",实现大模型与程序的连接,是 ChatOps 的基础。
  • Fine-tuning:用人工标注样本(CSV → JSONL)微调出一个日志紧急度分析专家,走通上传文件、创建任务、轮询状态、用微调模型推理的完整链路。
  • RAG / GraphRAG:分片 → embedding → 向量库 → 检索 → 生成,配合"不知道就说不知道"避免幻觉;RAG 回答不了全局性问题,GraphRAG 用知识图谱补齐,并可上 RAGFlow / QAnything 免写代码落地。
  • 本地部署:用 Ollama 一行命令本地跑起 Llama / Qwen / DeepSeek,Modelfile 定制 system prompt,并用兼容 OpenAI 的代码或 Open WebUI 的类 ChatGPT 前端消费它。

从这里出发,后面 Agent、基于大模型的故障诊断与自动修复、流量预测等模块,都建立在这套"会提问、会记忆、会调函数、会微调、会检索、能本地跑"的能力之上——它们是 AIOps 智能化的地基。

自测题与动手练习

面试官
什么是 token 和上下文窗口(context window)?为什么"提示语越长效果不一定越好"?
候选人
token 是大模型处理文本的最小单位,商用模型按 token 计费,所有输入(包括空格)都占 token。
上下文窗口 = 单次请求输入 token + 输出 token 的总量上限(如 GPT-4o 约 128K / 最大输出约 4096)。
效果不随 token 数量线性增长:塞入过多无关信息反而会把模型带偏,降低回答质量。所以提示语"够清楚即可",不是越多越好。
面试官
zero-shot 和 few-shot 的区别是什么?为什么工程上更推荐 few-shot?思维链(CoT)是怎么提升推理准确性的?
候选人
zero-shot 只用自然语言描述任务、不给示例;few-shot 会额外给若干"输入→期望输出"的参考示例,让模型对齐输出风格与判断逻辑。
few-shot 有了示例后生成往往更准确,所以日常更推荐。
CoT 让模型"先一步步思考、每步给解释"再给答案,像考试分步推导一样,提升结果的可解释性与推理准确性(zero/few-shot 都有效)。最简单用法是追加"让我们一步步思考,并为每一步做简单解释"。
面试官
大模型"无状态",那多轮对话的记忆是怎么实现的?手写实现与 LangChain 的 ConversationBufferMemory 有何异同?
候选人
每次推理都是独立 HTTP 请求、无状态。实现记忆 = 把上一次模型的 assistant 输出带回 messages,再追加新的 user 消息,下轮即可基于历史继续聊。
ConversationBufferMemory 底层逻辑和手写几乎一样(通过传递历史消息实现记忆),只是封装了管理。
注意:程序退出后上下文不自动保存;要跨会话保留需持久化到数据库再取回。想"只记最后 N 轮"用 ConversationBufferWindowMemory(设 k),或用 token 上限 / 对话总结来控制长度。
面试官
启用 JSON Mode 的两个前置条件是什么?它有哪些局限?
候选人
① 请求里声明 response_format={"type": "json_object"};② system 提示里必须包含 “json” 关键字,让模型知道在做 JSON 业务。
局限:JSON schema 是写死在 system 提示里的,字段变动就得改提示、且无法描述动态/复杂 schema、难以承载复杂业务逻辑。复杂场景应改用 Function Calling。
面试官
“Function Calling 让大模型直接调用函数”——这个说法对吗?它真正的价值与调用闭环是什么?
候选人
不对:大模型只能输出字符串,拿不到函数也无法执行副作用。
真正的闭环:你在 tools 定义函数(name/description/parameters/required)→ 模型根据语义选择函数并填充参数(返回 tool_calls,含函数名 + JSON 格式的 arguments)→ 你的代码真实调用该函数并拿到返回 → 把函数结果追加回 messages → 再做一次推理让模型基于结果作答。
价值在于实现大模型与程序的连接,也是 ChatOps 的基础。内部实现往往依赖 JSON Mode。
面试官
为什么需要 Fine-tuning?四个典型使用场景是什么?做微调前,数据要经过怎样的格式转换?
候选人
微调目的:用更多样本数据改进模型在特定任务上的表现。
四场景:①输出特定风格/格式;②提升输出可靠性;③纠正无法遵守复杂 system prompt;④执行难以用语言表达的新任务/新技能。
数据转换:把人工标注的 Excel/CSV(如日志 → 优先级)转换成 JSONL,每条数据含 system(设定角色/规则)+ user(日志内容)+ assistant(期望输出 P0/P1/P2)。有 N 条标注记录就有 N 条 JSONL。
面试官
用 OpenAI 微调日志专家,完整链路分哪几步?为什么微调完成后必须用 fine_tuned_model 而非原始模型名?
候选人
链路:准备数据 → 上传文件(files.create(purpose="fine-tune") 拿 file_id)→ 创建微调任务(fine_tuning.jobs.create(training_file=..., model="gpt-4o-mini-..."))→ 轮询状态(retrieve(job_id))→ 用返回的 模型 ID 做推理。
微调是异步的;fine_tuned_model 是任务完成后生成的专属于你的新模型 ID(形如 ft:gpt-4o-mini-...),用它才能调用微调后的参数,原始 gpt-4o-mini 是通用版本。
当前公开可微调主要是 gpt-4o-mini / gpt-3.5-turbo(gpt-4o 一般不开放);耗时取决于数据量。
面试官
RAG 由哪两个阶段构成?“分片向量化模型与检索向量化模型不一致"会造成什么问题?
候选人
阶段①建立索引:加载文档 → 分片 → embedding → 存向量库;阶段②检索与生成:用向量近似度匹配(余弦相似度/欧几里得距离)取相关块 → 拼进 system → 让模型作答。
如果分片时用的 embedding 模型和检索时不一致,向量维度都不同,根本无法计算相似度/匹配,会直接导致检索失效。所以两者必须保持一致。
system 里常写"不知道就说不知道"以避免 RAG 幻觉。
面试官
RAG 的两大短板是什么?GraphRAG 为什么能解决它们?
候选人
①分片"暴力"截断可能切断上下文逻辑关联 → 回答不了内在关联问题;②只能做字面近似检索,回答不了全局性/总结性问题(如"谁维护的服务最多")。
GraphRAG 把实体的关系提取出来构建知识图谱,检索时基于图谱把相关联的对象成簇取出给模型 → 能回答全局性问题,也兼容字面检索。实操用 graphrag index/query,可用 Neo4j 可视化实体关系,或直接上 RAGFlow / QAnything 免写代码落地。
面试官
用 Ollama 在本地跑起一个开源模型分哪几步?怎么定制 system prompt 并在代码里调用它?
候选人
①按文档安装 Ollama;②ollama run llama3.1:8b(或 qwen2.5:7b 等)一行拉起并推理——注意 CPU 推理时 7B/13B/33B 分别约需 8G/16G/32G 内存。
定制 system:写 Modelfile FROM 模型 + SYSTEM "..." → ollama create 名字 -f Modelfile → ollama run 名字。
代码调用:Ollama 的 REST API 兼容 OpenAI,用 openai SDK 时只需 base_url=http://localhost:11434/v1、api_key 填占位值即可。
要更好的交互界面,用 Docker 一键拉起 Open WebUI(默认 3000 端口),即可像 ChatGPT 一样选用本地模型对话、上传文件问答。

动手练习(建议真做一遍):

  1. 意图识别 Prompt:写一段"识别用户意图(查日志/查指标/运维操作)“的 zero-shot 提示跑一次,再加 3 个 few-shot 例子跑一次,对比输出稳定性;给它追加"让我们一步步思考"再观察一次。
  2. Chat Completions 接入:用 openai SDK 完成最简调用(system 设运维专家、user 问"Linux 端口占用怎么排查,给一条命令”),再升级成带多轮记忆的 while 循环并验证"你还记得刚才的命令吗"。
  3. Memory + JSON Mode:用 LangChain 的 ConversationBufferMemory 重写多轮对话;再用 response_format=json_object 写一个"从用户输入提取 service_name / action"的 JSON Mode 样例。
  4. Function Calling:定义一个 analyze_log(query) 函数 + 对应 tools,完整跑通"模型选函数填参 → 代码真实调用 → 把结果追加 → 二次推理"的闭环;再扩一个"获取 Prometheus 指标"的函数加到 available_functions。
  5. 微调:准备一份人工标注的日志 CSV,写脚本转 JSONL,上传后创建微调任务并轮询,用微调后的模型对 disk error、CPU 使用率过高 做紧急度分类验证。
  6. RAG / GraphRAG:建一份运维 Markdown 知识库,用 LangChain + ChromaDB 走通 RAG 并问"payment 由谁维护";再导入 GraphRAG 跑 index/query --method global 问"谁维护的服务最多"(若本地不便,可用 RAGFlow / QAnything 上传同一份文档对比问答效果)。
  7. 本地推理:ollama run qwen2.5:7b 本地拉起并对话;写一个 Modelfile 定制 system prompt 后 ollama create;再用 Docker 启动 Open WebUI(3000 端口),拖入同一份运维文档做文件问答。

本章小结

复习提示:
  • Prompt 工程:Prompt 决定输出质量。掌握 token / 上下文窗口;多给具体描述、重要事排序、标准化格式、拆解任务;优先 few-shot、善用 CoT;知识库动态放进 system prompt 是 RAG 的雏形;超长文档用分片 + 多次总结取舍准确性。
  • 接入大模型:openai SDK 的最简调用由 messages(system/user/assistant 三角色)+ model 构成;国内代理需配 base_url;key 走环境变量。
  • 记忆:HTTP 无状态 → 把 assistant 历史带回 messages 即可实现多轮记忆;LangChain 提供 ConversationBufferMemory / ConversationBufferWindowMemory(k) / token 上限 / 对话总结四类方式。程序退出不保留上下文,跨会话需持久化。
  • JSON Mode:response_format=json_object + system 含 “json” 关键字双前置条件;用于语义参数提取,但 schema 固定、难承载复杂逻辑。
  • Function Calling:模型只负责选函数 + 填参数,真正调用由你代码执行(tool_calls → 真实函数 → 结果回填 → 二次推理);tools 可传 N 个函数,扩展新能力只改 tools 不改 system,是实现大模型-程序连接与 ChatOps 的基础。
  • Fine-tuning:四类场景;数据从人工标注 CSV → JSONL(system+user+assistant);链路上传文件 → 建任务 → 轮询状态 → 用 fine_tuned_model 推理;公开可微调多为 gpt-4o-mini/gpt-3.5,数据质量是成败关键。
  • RAG:索引(加载→分片→embedding→存库)+ 检索生成(近似度匹配→取块→system→作答);分片与检索的 embedding 模型必须一致;配合"不知道就说不知道"防幻觉。
  • GraphRAG:提取实体关系构建知识图谱,解决 RAG 无法回答全局性/总结性问题;graphrag index/query + Neo4j 可视化;免写代码可用 RAGFlow(支持文档多/OCR/混合排序)与 QAnything(rerank 更准、文档类型支持较弱)。
  • 本地部署:Ollama 一行命令跑 Llama3.1 / Qwen / DeepSeek(注意内存门槛);Modelfile 定制 system 与接入任意 GGUF 模型;REST API 兼容 OpenAI 可在代码里直接调用;Open WebUI 提供类 ChatGPT 前端、支持文件/URL 对话、搜索引擎、多实例负载均衡、TTS/STT、ComfyUI 文生图等。
  • 本章把"会提问、会记忆、会调函数、会微调、会检索、能本地跑"这六项能力打全了——它们正是后续 Agent、故障诊断与自动化、流量预测等 AIOps 场景的地基,务必亲手敲通每条命令。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!