Malize's blog Malize's blog
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • Git 工具

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)

Malize

持续学习,持续成长
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • Git 工具

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)
  • 课程准备

  • 构造问答系统

    • 项目背景
    • 用大模型构建新人答疑机器人
      • 🚄 前言
      • 🍁 课程目标
      • ⚠️ 环境准备
      • 💻 1. 通过API调用千问
        • 1.1 确认 Python 环境
        • 1.2 获取 API Key
        • 1.3 配置 API Key
        • 1.4 基础对话调用
        • 1.5 多轮对话
        • 多轮对话的工作原理
        • 1.6 流式输出
        • 1.7 观察与思考
      • 📚 2. 大模型是如何工作的
        • 1.2. 大模型的文本生成工作流程
        • 2.2 影响大模型内容生成的随机性参数
        • 2.2.1 temperature:调整候选Token集合的概率分布
        • 2.2.2 top_p:控制候选Token集合的采样范围
        • 2.2.3 小结
      • ⚙️ 3. 让大模型能够回答私域知识问题
        • 3.1 初步方案:在提示词中“喂”入知识
        • 3.2 核心瓶颈:有限的上下文窗口
        • 3.3 解决之道:上下文工程 (Context Engineering)
        • 3.4 技术方案:RAG(检索增强生成)
        • 3.4.1 第一阶段:建立索引
        • 3.4.2 第二阶段:检索与生成
      • 📝 4. 本节小结
        • 扩展阅读
      • 🔥 课后小测验
        • 🔍 单选题 2.1.1
        • 📝 案例分析题 2.1.2
        • 🎯 场景A解决方案
        • 🎯 场景B解决方案
        • 🔍 单选题 2.1.3
        • 🔍 单选题 2.1.4
      • ✅评价反馈
    • 扩展答疑机器人的知识范围
    • 优化提示词改善答疑机器人回答质量
    • 自动化评测答疑机器人的表现
    • 优化 RAG 应用提升问答准确度
  • 构建Agent系统

  • 交付上线

  • 总结与展望

  • 大模型
  • 构造问答系统
malize
2026-01-03
目录

用大模型构建新人答疑机器人

# 2.1 用大模型构建新人答疑机器人

# 🚄 前言

你在一家教育内容开发公司工作,随着新员工的持续加入,频繁的答疑需求导致了显著的时间与资源成本。你决定使用大模型技术来构建一个答疑机器人,提升答疑的准确率与效率。

# 🍁 课程目标

学完本节课程后,你将能够:

  • 通过API调用千问
  • 学习大模型的工作原理
  • 了解大模型的局限性及其解决方案

# ⚠️ 环境准备

为了让你能够更流畅地体验教程内容,建议你首先完成 阿里云大模型高级工程师ACP认证课程 中的 环境准备 (opens new window) 章节,并确保课程所需的环境已经正确安装。

# 💻 1. 通过API调用千问

# 1.1 确认 Python 环境

体验大模型最直接的方式就是通过网页界面(如千问 (opens new window))与它对话。不过作为开发者,往往需要在自己的应用中集成大模型能力,你可以使用业界广泛采用的 OpenAI Python SDK 来调用千问大模型。你已经在1.0 计算环境准备安装了必要的依赖,在执行接下来的代码前,确认已经切换到新创建的python环境,如本例中创建的 Python (llm_learn)。

# 1.2 获取 API Key

为了调用千问,你需要前往阿里云大模型服务平台百炼,开通 模型调用服务 (opens new window) 并 创建一个API Key (opens new window)。

如果页面顶部显示如下,则表示你尚未开通百炼的模型调用服务。开通服务后可以调用模型。

请参考下图中的操作步骤来获取你的 API KEY :

# 1.3 配置 API Key

在使用 API 之前,你需要妥善处理 API Key 的安全问题。直接在代码中写入 API Key 是一个不好的习惯,因为这样很容易在分享代码时泄露密钥,并且在更换API Key后所有明文编码的API Key部分都需要修改。更安全、便捷的做法是将 API Key 存储在环境变量中。

下面的代码会从配置文件中加载你的 API Key,并将其设置为环境变量。代码执行后会显示 API Key 的前五位字符(后面用星号代替),这样你可以确认配置是否正确,同时又不会暴露完整的密钥。

# 加载百炼的 API Key 用于调用千问大模型
import os, sys
os.chdir(os.path.join(os.path.dirname(os.path.abspath('')), 'course_core'))
sys.path.insert(0, os.getcwd())

from config.load_key import load_key
load_key()
print(f'''你配置的 API Key 是:{os.environ["DASHSCOPE_API_KEY"][:5]+"*"*5}''')
1
2
3
4
5
6
7
8
  • 你需要 “回车” 确认你的输入,输入成功后你会看到“ 你配置的 API Key 是:sk-88***** ”的字样。
  • 如果你需要更换 “API-KEY”,请编辑上一级目录的 “KEY.json” 文件。
  • 如果你在使用VS-CODE,API-KEY的输入框会出现在窗体的顶部。

# 1.4 基础对话调用

先来尝试一个简单的对话。下面的代码创建了一个名为“公司小蜜”的助手,它可以回答关于公司运营的问题。你可以使用“选择项目管理工具”这个常见问题作为示例:

from openai import OpenAI
import os
client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def get_qwen_response(prompt):
    response = client.chat.completions.create(
        model="qwen-max",
        messages=[
            # system message 用于设置大模型的角色和任务
            {"role": "system", "content": "你负责教育内容开发公司的答疑,你的名字叫公司小蜜,你要回答同事们的问题。"},
            # user message 用于输入用户的问题
            {"role": "user", "content": prompt}
        ]
    )
    return response.choices[0].message.content
response = get_qwen_response("我们公司项目管理应该用什么工具")
print(response)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 1.5 多轮对话

在实际应用中,用户往往不会一次性提出完整的问题。想象一下这个场景:一位新员工先问「我们公司用什么项目管理工具」,得到回答后又接着问「那怎么申请账号呢」。如果大模型无法「记住」之前的对话,它就不知道用户问的是哪个工具的账号申请流程。

这就是多轮对话的意义所在:让大模型能够参考历史对话信息,理解上下文关联,从而给出连贯、准确的回复。

# 多轮对话的工作原理

在 OpenAI SDK 中,实现多轮对话的关键在于 messages 参数。这是一个列表,包含了对话的完整历史记录。每条消息都有两个关键字段:

  • role:消息的角色,可以是 system(系统指令)、user(用户输入)或 assistant(模型回复)
  • content:消息的具体内容

大模型会根据 messages 列表中的所有消息来生成回复,因此你需要将之前的对话历史(包括用户问题和模型回答)都保存在这个列表中。

def multi_turn_chat():
    # 初始化对话历史,包含系统提示词
    conversation_history = [
        {"role": "system", "content": "你负责教育内容开发公司的答疑,你的名字叫公司小蜜,你要回答同事们的问题。"}
    ]
    
    # 模拟多轮对话
    user_questions = [
        "我们公司项目管理应该用什么工具?",
        "那我怎么申请这个工具的账号呢?",
        "申请一般需要多久能批下来?"
    ]
    
    for question in user_questions:
        print(f"👤 用户:{question}")
        
        # 将用户问题添加到对话历史
        conversation_history.append({"role": "user", "content": question})
        
        # 调用大模型,传入完整的对话历史
        response = client.chat.completions.create(
            model="qwen-max",
            messages=conversation_history  # 包含所有历史消息
        )
        
        # 获取模型回复
        assistant_message = response.choices[0].message.content
        print(f"🤖 小蜜:{assistant_message}\n")
        
        # 将模型回复也添加到对话历史,以便下一轮对话使用
        conversation_history.append({"role": "assistant", "content": assistant_message})

multi_turn_chat()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

运行上面的代码,你会发现大模型能够理解"这个工具"指的是之前提到的项目管理工具,并针对性地回答账号申请和审批流程的问题。这就是多轮对话的作用。

💡 小贴士:

  • 对话历史会占用上下文窗口的空间。如果对话轮次过多,可能需要对历史消息进行截断或摘要处理。
  • 在生产环境中,通常需要将对话历史持久化存储(如数据库),以支持用户跨会话的连续对话体验。
  • 如需了解更多多轮对话的最佳实践,可以参考阿里云百炼多轮对话文档 (opens new window)。

# 1.6 流式输出

运行上面的代码后,你会注意到需要等待一段时间(约20秒)才能看到完整的回复。这是因为默认情况下,API 会等待模型生成完所有内容后才一次性返回结果。在实际应用中,这种等待可能会影响用户体验 —— 想象一下用户盯着一个空白界面等待20秒的场景!

幸运的是,你可以使用"流式输出"(Streaming)来优化这个问题。使用流式输出时,模型会像人类打字一样,一边思考一边输出,让用户能够立即看到部分回复,大大提升交互体验。接下来,看看如何实现流式输出...

💡 小贴士:流式输出只是改变了内容的展示方式,模型的思考过程和最终答案的质量都保持不变。你可以放心使用这个功能来优化你的应用体验。

要实现流式输出,只需在之前的代码基础上添加 stream=True 参数,并调整输出方式:

def get_qwen_stream_response(user_prompt,system_prompt):
    response = client.chat.completions.create(
        model="qwen-max",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        stream=True
    )
    for chunk in response:
        yield chunk.choices[0].delta.content

response = get_qwen_stream_response(user_prompt="我们公司项目管理应该用什么工具",system_prompt="你负责教育内容开发公司的答疑,你的名字叫公司小蜜,你要回答同事们的问题。")
for chunk in response:
    print(chunk, end="")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 1.7 观察与思考

通过对千问大模型的两次提问,你可能会发现一些有趣的现象:

  1. 即使问题完全相同,每次的回答都略有不同。这是大模型的特性之一,就像人类一样,它会用不同的方式表达相似的观点。
  2. 模型给出的建议集中在一些通用的项目管理工具上,比如 Jira、Trello 或 Asana。虽然大模型知识丰富,但它并不了解你公司的具体情况,比如已有的工具链、团队规模、预算限制等。

公司使用的项目管理软件资料可以从docs/内容开发工程师岗位指导说明书.pdf文件中找到。

这两个现象其实很有意思!为什么大模型会表现出这样的特点呢?要理解这一点,需要掀开大模型的“神秘面纱”,看看它是如何思考和工作的。别担心,你将通过简单直观的方式来理解这些概念。

# 📚 2. 大模型是如何工作的

近几十年来,人工智能经历了从基础算法到生成式AI的深刻演变。生成式AI通过学习大量数据可以创造出全新的内容,如文本、图像、音频和视频,这极大地推动了AI技术的广泛应用。常见的应用场景包括智能问答(如千问、GPT)、创意作画(如Stable Diffusion)以及代码生成(如灵码)等,涵盖了各个领域,让AI触手可及。

智能问答作为大模型最经典且广泛的应用之一,是我们探索大模型工作机制的最佳范例。接下来将介绍大模型在问答场景中的工作流程,帮助你更深入地理解其背后的技术原理。

# 1.2. 大模型的文本生成工作流程

让我们以一个简单的输入"ACP is a very"为例,来具体看看大模型是如何一步步处理这个输入,并最终生成完整句子的。

大模型的文本生成流程可以分为文本分词、Token 向量化、大模型推理、解码与自回归、输出文本五个阶段:

第一阶段:文本分词

计算机无法直接理解人类的文字。因此,第一步需要将我们输入的文本“ACP is a very”转换成计算机能够处理的数字格式。这个过程被称为Tokenization(分词)。

Token 是分词器(Tokenizer)把文本编码后得到的基本单元,每个 token 对应词表中的一个整数 ID。需要注意的是,token 通常是子词片段或字符片段,不一定等于一个完整的"词",也不一定具有独立语义。比如英文单词可能被拆成词根和后缀,中文可能按字或常见词组切分,空格和标点也可能被编码进 token。

以"ACP is a very"为例,Tokenizer 会将其编码成若干 token,并为每个 token 分配一个 ID。如果你对千问(Qwen)的分词细节感兴趣,可以查阅其官方文档:Tokenization 说明 (opens new window)。

第二阶段:Token 向量化

虽然我们得到了数字 ID 序列,但这些 ID 本身的数值大小并没有实际意义(比如,ID 500 并不意味着它在语义上就比 ID 50 更“重要”或“相关”)。为了让模型理解 token 的真实含义,我们需要将这些离散的 ID 转换成包含丰富语义信息的数学表示——向量 (Vector)。

这个转换通过一个被称为 Embedding 矩阵 的巨大表格完成。简单来说,就是用 token ID 在这个大表格里“查表”,取出对应的那一整行向量。每个 token 的向量都包含了其在多维度语义空间中的坐标。

此外,语言的顺序至关重要(“我帮你”和“你帮我”截然不同)。但基础的 Transformer 模型本身并不直接处理时序信息。因此,还需要额外给每个 token 向量添加位置信息 (Positional Encoding),让模型能够区分出 “我” 是第一个词,“你” 是第三个词,以此类推。

第三阶段:大模型推理

现在,携带了语义和位置信息的向量序列被送入 Transformer 模型的解码器层 (Decoder Blocks) 进行计算。这个过程被称为前向计算 (Forward Pass)。

向量序列会逐层穿过数十个甚至上百个结构相似的解码器层,在因果自注意力机制 (Causal Self-Attention) 和前馈神经网络 (Feed-Forward Network) 的作用下,文本向量中的信息被一次一次压缩和提取。我们最终会得到最后一个 token(也就是 "very")所对应的隐藏状态向量 (Hidden State)。这个向量可以被认为是模型在阅读了 “ACP is a very” 之后,对接下来可能出现的内容的一个高度浓缩的“思考总结”。最后,这个“思考总结”向量会通过一个线性投影层,映射到整个词汇表的维度,得到一个庞大的分数向量 (Logits)。

第四阶段:解码与自回归

在得到 logits 之后,大模型还需要经过 softmax 函数计算,将这些 logits 重新映射为一种概率分布 $P(\text{next_token} \mid \text{context})$,表示每个 token 被选中的概率。最后,大模型会根据一个解码策略来决定最终输出哪个 token。解码策略主要分为两类:

  • 近似确定性解码:如贪心解码(Greedy Decoding),每次选概率最高的 token,或 Beam Search 保留多个候选路径。
  • 随机采样解码:如 Top-p(Nucleus Sampling)、Top-k Sampling,从高概率的候选集合中随机抽取。

很多在线服务默认使用随机采样解码,因此即使输入完全相同,也可能出现"回答略有不同"的现象。

通过调整 temperature,你可以改变 softmax 输出概率分布的"尖锐程度"。temperature 越低,token 之间的概率差异越大,概率分布越集中于少数的高概率 token,模型输出越确定。temperature 越高,输出概率分布越平坦,模型的输出多样性越高。配合 top_p(控制参与采样的候选集合范围)等参数,你可以在模型输出的多样性与稳定性之间做权衡。你可以在下一小节中进一步了解这些参数。

例如,下图展示了模型预测出的部分候选 token 及其概率(实际词汇表远比这大得多):

一旦选定了下一个 token(比如模型选择了 "informative"),这个新生成的 token 就会被追加 (append) 到原始输入序列的末尾,形成新的输入 “ACP is a very informative”。然后,模型会基于这个新序列,重复第三和第四阶段,继续预测下一个 token。这个"文字接龙"的过程称为自回归生成(Autoregressive Generation)。

模型的自回归循环会持续进行,直到满足某个停止条件:

  • 生成了特殊的终止符 (End-of-Sequence, EOS token)。
  • 达到了预设的最大生成长度限制。
  • 生成了用户指定的停用词序列。

不同模型系列的终止符不同。

模型终止符
Deepseek V3.2<|end▁of▁sentence|>
GPT 系列<|endoftext|>
GPT-OSS<|return|>
LLaMA/Mistral < /s>
Pad Token<|endoftext|>
Qwen3<|im_end|>

第五阶段:输出文本

最后,系统会将整个生成过程中的 token ID 序列转换回人类可读的字符串,并呈现给我们。

我们经常看到的流式输出 (Streaming) 效果,其实是服务端每生成一个或几个 token,就立刻将其解码并增量地发送到你的界面上。由于 token 可能是子词片段,所以在流式显示时,有时会看到一个词被分成几部分输出,或者空格的出现时机看起来有些奇怪,这都是正常现象。

# 2.2 影响大模型内容生成的随机性参数

假设在一个对话问答场景中,用户提问为:“在大模型ACP课程中,你可以学习什么?”。为了模拟大模型生成内容的过程,我们预设了一个候选Token集合,这些Token分别为:“RAG”、“提示词”、“模型”、“写作”、“画画”。大模型会从这5个候选Token中选择一个作为结果输出(next-token),如下所示。

用户提问:在大模型ACP课程中,你可以学习什么?

大模型回答:RAG

在这个过程中,有两个重要参数会影响大模型的输出:temperature 和 top_p,它们用来控制大模型生成内容的随机性和多样性。下面介绍这两个参数的工作原理和使用方式。

# 2.2.1 temperature:调整候选Token集合的概率分布

在大模型生成下一个词(next-token)之前,它会先为候选Token计算一个初始概率分布。这个分布表示每个候选Token作为next-token的概率。temperature是一个调节器,它通过改变候选Token的概率分布,影响大模型的内容生成。通过调节这个参数,你可以灵活地控制生成文本的多样性和创造性。

为了更直观地理解,下图展示了不同temperature值对候选Token概率分布的影响。绘图代码放置在 /resources/2_1 文件目录下。

图中的低、中、高温度基于Qwen-Max模型的范围[0, 2)划分。

由上图可知,温度从低到高(0.1 -> 0.7 -> 1.2),概率分布从陡峭趋于平滑,候选Token“RAG”从出现的概率从0.8 -> 0.6 -> 0.3,虽然依然是出现概率最高的,但是已经和其它的候选Token概率接近了,最终输出也会从相对固定到逐渐多样化。

针对不同使用场景,可参考以下建议设置 temperature 参数:

  • 明确答案(如生成代码):调低温度。
  • 创意多样(如广告文案):调高温度。
  • 无特殊需求:使用默认温度(通常为中温度范围)。

需要注意的是,当 temperature=0 时,虽然会最大限度降低随机性,但无法保证每次输出完全一致。如果想深入了解,可查阅 temperature的底层算法实现 (opens new window)。

下面体验temperature的效果。通过调整temperature值,对同一问题提问 10 次,观察回答内容的波动情况。

temperature的示例代码跟接下来要讲解的top_p类似,先封装以供后续使用。

import time

def get_qwen_stream_response(user_prompt, system_prompt, temperature, top_p):
    response = client.chat.completions.create(
        model="qwen-max",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        temperature=temperature,
        top_p=top_p,
        stream=True
    )
    
    for chunk in response:
        yield chunk.choices[0].delta.content

# temperature,top_p的默认值使用Qwen-Max模型的默认值
def print_qwen_stream_response(user_prompt, system_prompt, temperature=0.7, top_p=0.8, iterations=10):
    for i in range(iterations):
        print(f"输出 {i + 1} : ", end="")
        ## 防止限流,添加延迟
        time.sleep(0.5)
        response = get_qwen_stream_response(user_prompt, system_prompt, temperature, top_p)
        output_content = ''
        for chunk in response:
            output_content += chunk
        print(output_content)

# Qwen-Max模型:temperature的取值范围是[0, 2),默认值为0.7
# 设置temperature=0
print_qwen_stream_response(user_prompt="马也可以叫做", system_prompt="请帮我续写内容,字数要求是4个汉字以内。", temperature=0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 设置temperature=1.9
print_qwen_stream_response(user_prompt="马也可以叫做", system_prompt="请帮我续写内容,字数要求是4个汉字以内。", temperature=1.9)
1
2

从实验中可以明显观察到,温度值越高,模型生成的内容更具变化和多样性。

# 2.2.2 top_p:控制候选Token集合的采样范围

top_p 是一种筛选机制,用于从候选 Token 集合中选出符合特定条件的“小集合”。具体方法是:按概率从高到低排序,选取累计概率达到设定阈值的 Token 组成新的候选集合,从而缩小选择范围。

下图展示了不同top_p值对候选Token集合的采样效果。

图示中蓝色部分表示累计概率达到top_p阈值(如0.5或0.8)的Token,它们组成新的候选集合;灰色部分则是未被选中的Token。

当top_p=0.5时,模型优先选择最高概率的Token,即“RAG”;而当top_p=0.8时,模型会在“RAG”、“提示词”、“模型”这三个Token中随机选择一个生成输出。

由此可见,top_p值对大模型生成内容的影响可总结为:

  • 值越大 :候选范围越广,内容更多样化,适合创意写作、诗歌生成等场景。
  • 值越小 :候选范围越窄,输出更稳定,适合新闻初稿、代码生成等需要明确答案的场景。
  • 极小值(如 0.0001):理论上模型只选择概率最高的 Token,输出非常稳定。但实际上,由于分布式系统、模型输出的额外调整等因素可能引入的微小随机性,仍无法保证每次输出完全一致。

下面体验top_p的效果。通过调整top_p值,对同一问题提问10次,观察回答内容的波动情况。

# Qwen-Max模型:top_p取值范围为(0,1],默认值为0.8。
# 设置top_p=0.001
print_qwen_stream_response(user_prompt="为一款智能游戏手机取名,可以是", system_prompt="请帮我取名,字数要求是4个汉字以内。", top_p=0.001)
1
2
3
# 设置top_p=0.8
print_qwen_stream_response(user_prompt="为一款智能游戏手机取名,可以是",system_prompt="请帮我取名,字数要求是4个汉字以内。", top_p=0.8)
1
2

根据实验结果可以发现,top_p值越高,大模型的输出结果随机性越高。

# 2.2.3 小结

是否需要同时调整temperature和top_p?

为了确保生成内容的可控性,建议不要同时调整top_p和temperature,同时调整可能导致输出结果不可预测。你可以优先调整其中一种参数,观察其对结果的影响,再逐步微调。


知识延展:top_k
在千问系列模型中,参数top_k也有类似top_p的能力,可查阅千问API文档 (opens new window)。它是一种采样机制,从概率排名前k的Token中随机选择一个进行输出。一般来说,top_k越大,生成内容越多样化;top_k越小,内容则更固定。当top_k设置为1时,模型仅选择概率最高的Token,输出会更加稳定,但也会导致缺乏变化和创意。

知识延展:seed
在千问系列模型中,参数seed也支持控制生成内容的确定性,可查阅千问API文档 (opens new window)。在每次模型调用时传入相同的seed值,并保持其他参数不变,模型会尽最大可能返回相同结果,但无法保证每次结果完全一致。

设置 temperature、top_p、seed 控制大模型输出,为何仍存在随机性?

即使将 temperature 设置为 0、top_p 设置为极小值(如 0.0001),并使用相同的 seed ,同一个问题的生成结果仍可能出现不一致。这是因为一些复杂因素可能引入微小的随机性,例如大模型运行在分布式系统中,或模型输出引入了优化。

举个例子: 分布式系统就像用不同的机器切面包。虽然每台机器都按照相同的设置操作,但由于设备之间的细微差异,切出来的面包片可能还是会略有不同。

# ⚙️ 3. 让大模型能够回答私域知识问题

回到最初的挑战:答疑机器人无法回答“我们公司项目管理用什么工具”这类内部问题。根本原因在于,大模型的知识来源于其训练数据,这些数据通常是公开的互联网信息,不包含任何特定公司的内部文档、政策或流程。

你可以把大模型想象成一台刚出厂的超级计算机:它的 CPU(推理能力)极其强大,硬盘(模型权重)里也预装了海量的通用知识。但对于你公司的“内部资料”,它的硬盘里是空白的。

面对这个问题,最直观的解决思路就是:在运行时,把公司的内部知识临时告诉它。

# 3.1 初步方案:在提示词中“喂”入知识

你可以来验证这个思路:将公司项目管理工具的说明文档,直接添加到给模型的指令(System Prompt)中,作为背景知识提供给它。

💡 公司使用的项目管理软件资料可以从docs/内容开发工程师岗位指导说明书.pdf文件中找到。


user_question = "我是软件一组的,请问项目管理应该用什么工具"

knowledge = """公司项目管理工具有两种选择:
  1. **Jira**:对于软件开发团队来说,Jira 是一个非常强大的工具,支持敏捷开发方法,如Scrum和Kanban。它提供了丰富的功能,包括问题跟踪、时间跟踪等。

  2. **Microsoft Project**:对于大型企业或复杂项目,Microsoft Project 提供了详细的计划制定、资源分配和成本控制等功能。它更适合那些需要严格控制项目时间和成本的场景。
  
  在一般情况下请使用Microsoft Project,公司购买了完整的许可证。软件研发一组、三组和四组正在使用Jira,计划于2026年之前逐步切换至Microsoft Project。
"""

response = get_qwen_stream_response(
    user_prompt=user_question,
    # 将公司项目管理工具相关的知识作为背景信息传入系统提示词
    system_prompt="你负责教育内容开发公司的答疑,你的名字叫公司小蜜,你要回答学员的问题。"+ knowledge,
    temperature=0.7,
    top_p=0.8
)

for chunk in response:
    print(chunk, end="")
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

实验成功了!在提示词中加入相关的背景知识,大模型确实能够准确回答关于公司内部工具的问题。这个发现令人振奋,似乎你已经找到了解决私域知识问答的钥匙。

然而,当你试图将更多的公司文档(例如几十页的员工手册、上百页的技术规范)都用这种方式“喂”给大模型时,一个新的、更严峻的挑战出现了。

# 3.2 核心瓶颈:有限的上下文窗口

大模型接收我们输入(包括指令、问题和背景知识)的地方,被称为上下文窗口(Context Window)。你可以把它理解为计算机的“内存(RAM)”——它的容量是有限的。

你无法将整个公司的知识库(成百上千份文档)一次性塞进这个有限的窗口里。一旦输入内容超过模型的最大限制,就会导致错误。

这引出了一个核心问题:你需要对放入上下文窗口的内容进行筛选和管理。

# 3.3 解决之道:上下文工程 (Context Engineering)

简单粗暴地将信息塞进上下文,除了会超出窗口限制外,还会带来一系列“隐性”问题:

  1. 效率低:上下文越长,大模型处理所需的时间就越长,导致用户等待时间增加。
  2. 成本高:大部分模型是按输入和输出的文本量计费的,冗长的上下文意味着更高的成本。
  3. 信息干扰:如果上下文中包含了大量与当前问题无关的信息,就像在开卷考试时给了考生一本错误科目的教科书,反而会干扰模型的判断,导致回答质量下降。

你一定会意识到,成功的关键不在于“喂”给模型多少知识,而在于“喂”得有多准。

如何在正确的时间,将最相关、最精准的知识,动态地加载到大模型有限的上下文窗口中?——这门系统性地设计、构建和优化上下文的实践,就是上下文工程(Context Engineering)。

从这个角度看,许多大模型应用的失败,并非模型本身不够智能,而是“上下文”的失败。上下文工程正是释放大模型潜力的关键所在。

那么,上下文工程具体包含哪些技术呢?你可以通过下面这张概念图来快速了解它的核心版图:

💡 上下文工程 (Context Engineering) 的核心技术

它是构建可靠、高效大模型应用的一系列关键技术的总和,主要包括:

  • RAG (检索增强生成):从外部知识库(如公司文档)中检索信息,为模型提供精准的回答依据。
  • Prompt (提示词工程):通过精心设计的指令,精确地引导模型的思考方式和输出格式。
  • Tool (工具使用):赋予模型调用外部工具(如计算器、搜索引擎、API)的能力,以获取实时信息或执行特定任务。
  • Memory (记忆机制):为模型建立长短期记忆,使其能够在连续对话中理解历史上下文。
Context Engineering Diagram

你可能已经注意到,这里出现了一些新名词。别担心,你不需要立刻掌握所有细节。这里展示这张图的目的是为了让你建立一个整体认知:接下来要学习的几个知识,都是上下文工程的一部分。

为了让你更容易理解和吸收,在后续的课程中,这些概念将作为独立的主题,逐一详细拆解和实践。

现在,你需要聚焦于当前最紧迫的问题——如何解决私域知识问答。在这个技术版图中,RAG 是最直接、最有效的解决方案。接下来,你将深入学习它。

# 3.4 技术方案:RAG(检索增强生成)

RAG(Retrieval-Augmented Generation,检索增强生成) 就是实现上下文工程的强大技术方案。它的核心思想是:

在用户提问时,不再将全部知识库硬塞给大模型,而是先自动检索出与问题最相关的私有知识片段,然后将这些精准的片段与用户问题合并后,一同传给大模型,从而生成最终的答案。这样既避免了提示词过长的问题,又能确保大模型获得相关的背景信息。

构建一个 RAG 应用通常会分为两个阶段

# 3.4.1 第一阶段:建立索引

建立索引是为了将私有知识文档或片段转换为可以高效检索的形式。通过将文件内容分割并转化为多维向量(使用专用 Embedding 模型),并结合向量存储保留文本的语义信息,方便进行相似度计算。向量化使得模型能够高效检索和匹配相关内容,特别是在处理大规模知识库时,显著提高了查询的准确性和响应速度。

这些向量经过 Embedding 模型处理后不仅很好地捕捉文本内容的语义信息,而且由于语义已经向量化,标准化,便于之后与检索语义向量进行相关度计算。

# 3.4.2 第二阶段:检索与生成

检索生成是根据用户的提问,从索引中检索相关的文档片段,这些片段会与提问一起输入到大模型生成最终的回答。这样大模型就能够回答私有知识问题了。

总的来说,基于 RAG 结构的应用,既避免了将整个参考文档作为背景信息输入而导致的各种问题,又通过检索提取出了与问题最相关的部分,从而提高了大模型输出的准确性与相关性。

# 📝 4. 本节小结

在本节课程中,我们学习了以下内容:

  • 如何使用大模型 API
    通过实际的代码示例,我们掌握了如何通过 API 调用千问大模型,并学习了如何通过流式输出来优化用户体验。
  • 大模型的工作原理与局限性
    我们初步探索了大模型如“黑盒”般的工作流程,理解了其回答具有随机性的原因,并学习了如何通过调整 temperature 和 top_p 等参数来控制生成内容。更重要的是,我们直面了大模型的核心局限:其知识完全依赖于训练数据,无法获知你公司的内部文档等私域知识。
  • 上下文工程(Context Engineering)与 RAG 的核心思想 为了解决大模型的知识局限性,我们首先尝试了直接在提示词中“投喂”知识的方法,但很快就遇到了“上下文窗口限制”的瓶颈。这引出了一个至关重要的概念——上下文工程(Context Engineering),即智能地为大模型筛选、组织和提供最相关背景信息的核心方法论。作为该思想的具体实现,我们学习了 RAG(检索增强生成) 技术方案。通过“先检索、后生成”的模式,RAG 能有效地将精准的外部知识与大模型结合,从而在不超出限制的前提下,让模型能够回答私域知识问题。

除了本节课程中的示例展示的任务之外,你还可以让大模型完成更多类型的任务,如内容生成、结构化信息提取、文本分类、情感分析等。同时在你的大模型应用中引入 RAG 方案能够扩展大模型所能处理的知识范围。

下一小节,我们将动手实践,学习如何创建一个完整的 RAG 应用。

# 扩展阅读

  • 在学习本课程的时候,如果你想了解更多相关概念和原理,可以尝试让大模型进一步展开说明或给出学习建议:

千问支持开启 enable_search 参数,这个参数可以让大模型在生成回答时利用互联网搜索结果来丰富其回复内容。

completion = client.chat.completions.create(
    model="qwen-max",  # 此处以qwen-max为例,可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "巴黎奥运会中国队金牌数"},
    ],
    extra_body={"enable_search": True},
)
print(completion.choices[0].message.content)
1
2
3
4
5
6
7
8
9
  • 阿里云的推理模型 Qwen3 (opens new window) 具有强大的推理能力,模型会先输出思考过程,再输出回答内容。
reasoning_content = ""  # 定义完整思考过程
answer_content = ""     # 定义完整回复
is_answering = False   # 判断是否结束思考过程并开始回复

# 创建聊天完成请求
completion = client.chat.completions.create(
    model="qwen3-235b-a22b-thinking-2507",  # 此处以 qwen3-235b-a22b-thinking-2507 为例,可按需更换模型名称
    messages=[
        {"role": "user", "content": "9.9和9.11谁大"}
    ],
    stream=True,
    # 解除以下注释会在最后一个chunk返回Token使用量
    # stream_options={
    #     "include_usage": True
    # }
)

print("\n" + "=" * 20 + "思考过程" + "=" * 20 + "\n")

for chunk in completion:
    # 如果chunk.choices为空,则打印usage
    if not chunk.choices:
        print("\nUsage:")
        print(chunk.usage)
    else:
        delta = chunk.choices[0].delta
        # 打印思考过程
        if hasattr(delta, 'reasoning_content') and delta.reasoning_content != None:
            print(delta.reasoning_content, end='', flush=True)
            reasoning_content += delta.reasoning_content
        else:
            # 开始回复
            if delta.content != "" and is_answering is False:
                print("\n" + "=" * 20 + "完整回复" + "=" * 20 + "\n")
                is_answering = True
            # 打印回复过程
            print(delta.content, end='', flush=True)
            answer_content += delta.content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  • 如果你对多模态大模型感兴趣,可以参考:

    • 视觉理解 (opens new window)
    • 音频理解 (opens new window)
    • 全模态 (opens new window)
    • 文生图 (opens new window)
    • 视频生成 (opens new window)

# 🔥 课后小测验

# 🔍 单选题 2.1.1

你开发的答疑机器人上线后,用户反馈"每次问同一个问题,得到的回答都不一样,有时候内容还互相矛盾"。排查发现当前使用的是默认参数配置,你应该优先调整什么来降低输出的随机性❓
  • A. 将 temperature 调低至接近 0,使模型倾向于选择概率最高的 Token
  • B. 将 top_p 调高至接近 1,扩大候选 Token 的采样范围
  • C. 在 system prompt 中追加"请保持回答一致"的指令约束
  • D. 切换为参数量更大的模型,因为大模型的输出稳定性更高

【点击查看答案】

✅ 参考答案:A 📝 解析: temperature 控制 softmax 输出概率分布的"尖锐程度"。调低 temperature 会让概率集中在少数高概率 Token 上,从而使输出更加确定和稳定。选项 B 会扩大采样范围,反而增加随机性;选项 C 的提示词约束无法从根本上改变采样机制的随机行为;选项 D 中模型参数量与输出随机性无直接关系。

# 📝 案例分析题 2.1.2

小明开发写作助手遇到以下两个场景,他应该如何解决问题❓
  • 场景 🅰️ 生成内容缺乏创意:每次让模型写一篇关于“人工智能发展”的文章时,生成的内容都非常相似。
  • 场景 🅱️ 生成内容偏离主题:让模型写一份技术文档时,生成的内容经常加入一些不相关的内容。

请问:

  1. 基于本节课程中学习的大模型工作流程,这两个场景的问题产生的原因可能是什么?
  2. 应该如何调整 temperature 或 top_p 参数来解决这些问题?

【点击查看答案】

# 🎯 场景A解决方案

🔍 原因分析
temperature 值过低(如0.3),导致模型选择单一,生成内容缺乏多样性。

⚙️ 参数调整

temperature = 0.7~0.9  # 提升创意性
top_p = 0.9            # 增大选词范围
1
2

# 🎯 场景B解决方案

🔍 原因分析
temperature 过高(如1.2)或 top_p 过大

⚙️ 参数调整

temperature = 0.5~0.7  # 降低随机性
top_p = 0.7~0.8        # 聚焦高概率词
1
2

🌟 调参小技巧
每次调整幅度建议 ±0.2,通过AB测试观察效果变化。 如果需要兼顾场景A和场景B,推荐组合:temperature=0.6 + top_p=0.8

# 🔍 单选题 2.1.3

你在测试答疑机器人的多轮对话功能。用户先问"我们公司用什么项目管理工具",机器人回答了 Jira。用户接着问"怎么申请账号",但机器人回答的是通用的账号申请流程,完全没有提到 Jira。最可能的原因是什么❓
  • A. 第二次调用时没有把之前的对话历史放入 messages 数组,模型无法理解"账号"指的是 Jira 账号
  • B. 流式输出模式下模型只能处理当前轮次的输入,无法读取之前的 system prompt 内容
  • C. 模型的上下文窗口已满,自动丢弃了第一轮对话中包含 Jira 信息的 assistant 消息
  • D. 大模型的 temperature 设置过高,导致模型在第二轮对话中"遗忘"了之前提到的工具名称

【点击查看答案】

✅ 参考答案:A 📝 解析: 大模型 API 是无状态的,每次调用都是独立的。如果第二次调用时只传入了"怎么申请账号",模型无从知道用户指的是哪个工具的账号。选项 B 错误,流式输出只是改变内容的展示方式,不影响模型对 messages 数组的处理;选项 C 不太可能,两轮简短对话远不会填满上下文窗口;选项 D 错误,temperature 控制的是输出的随机性,不会导致模型"遗忘"上下文中明确提供的信息。

# 🔍 单选题 2.1.4

你需要用大模型从用户反馈中提取结构化信息(如产品名称、问题类型、严重程度),要求每次提取结果稳定一致。下列参数配置最合理的是哪个❓
  • A. temperature=1.5, top_p=0.9 —— 高创造力配置,让模型灵活理解用户表述
  • B. temperature=0.1, top_p=0.3 —— 低随机性配置,让模型尽量选择概率最高的 Token
  • C. temperature=0.7, top_p=0.8 —— 默认均衡配置,适用于大多数通用对话场景
  • D. temperature=0.1, top_p=0.95 —— 同时调低温度并放宽采样范围以兼顾稳定和多样

【点击查看答案】

✅ 参考答案:B 📝 解析: 结构化信息提取属于明确答案类任务,需要输出高度稳定。temperature 和 top_p 都应调低,使模型集中选择高概率 Token。选项 A 的高 temperature 会引入大量随机性;选项 C 的默认配置虽然通用但随机性仍偏高;选项 D 同时调整两个参数且方向相反(温度低但采样范围宽),可能导致输出行为不可预测,且课程建议不要同时调整这两个参数。

# ✅评价反馈

欢迎你参与阿里云大模型ACP课程问卷 (opens new window) 反馈学习体验和课程评价。 你的批评和鼓励都是我们前进的动力!

编辑 (opens new window)
#大模型#ACP认证#阿里云
项目背景
扩展答疑机器人的知识范围

← 项目背景 扩展答疑机器人的知识范围→

最近更新
01
Docker 核心命令大全
07-04
02
CIM半导体行业业务流程详解 原创
07-04
03
死锁 原创
04-15
更多文章>
Theme by Vdoing | Copyright © 2023-2026 Malize | GitHub | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式