项目一要求
构建一个多功能角色聊天机器人
- 目标:熟练使用OpenAI API和Prompt Engineering
- 任务:创建一个简单的Web界面(用Streamlit),用户可以选择不同的角色:“Python编程助手”、“英语口语教练”、“苏格拉底”,程序会根据选择,使用不同的System Prompt与用户进行高质量对话
- 收获:精通API调用、掌握核心的提示工程技巧
代码实现
本项目较为简单,故直接采用 Streamlit 框架实现一个简单的前端页面进行展示。
本项目涉及到的 Streamlit 语法都会在后文讲述。当然代码实现过程也有相应的一些解释,方便理解

1. Prompt 设计
现在要设计三个角色:CET6考试专家、python编程助手以及苏格拉底。
按照前文所学,可采用 RASCEF 框架编写 python 编程助手 prompt,使用 BPS-TAO 编写其余两个 prmopt。
框架复习:
RASCEF
- Role(角色):定义AI在交互中所扮演的角色,如电子邮件营销人员、项目经理等
- Action(行动):明确AI需要执行的具体行动,如编写项目计划或回答客户咨询
- Script(步骤):提供AI完成任务时应遵循的步骤,确保任务的有序进行
- Content(上下文):提供背景信息或情境,帮助AI理解任务的背景和环境
- Example(示例):通过具体实例展示期望的语气和风格,帮助AI更好地模仿和理解
- Format(格式):设定AI输出的格式,如段落、列表或对话,以适应不同的沟通需求
BPS-TAO
背景 Background:介绍与任务紧密相关的背景信息。
目的 Purpose:明确指出您期望LLM完成的具体任务。
风格 Style:指定您希望 LLM 输出的写作风格。
语气 Tone:定义输出内容应有的语气,比如正式、诙谐、温馨、关怀等,以便适应不同的使用场景和使用目的。
受众 Audience:明确指出内容面向的读者群体。
输出 Output:规定输出内容的具体形式,确保LLM提供的成果能直接满足后续应用的需求,比如列表、JSON数据格式、专业分析报告等形式。
使用框架编写好 prompt 后,可使用阿里云的 prompt 优化工具或者大模型(如 ChatGPT)进行二次优化和扩展。
CET6考试助手 prompt
CET_teacher_prompt = """
# Role (Background)
你是一位拥有15年经验的资深英语六级(CET-6)备考教练。你精通中国大学生英语考试的出题逻辑,擅长“词汇语境记忆法”和“长难句语法拆解”。你非常熟悉六级考试评分标准(满分710分,及格线425分)。
# Audience
你的学生目前六级模拟考成绩在380分左右(满分710),属于基础薄弱但渴望过级的考生。
- 特点:词汇量不足(约3500-4000),语法概念模糊,做题主要靠语感,容易被干扰项误导。
- 需求:不仅需要答案,更需要基础知识的补强和解题技巧的点拨。
# Purpose (Problem)
你的任务是协助用户攻克六级笔试中的阅读、完型、翻译和写作。
1. 深度解析:不仅仅给出正确选项,必须解释为什么其他选项是错的。
2. 基础补救:针对题目中出现的高频词汇和长难句,进行详细的中文释义和语法结构拆解。
3. 技巧传授:教授如何通过上下文线索、逻辑关系词来快速锁定答案,提升做题速度。
# Style
1. 结构化思维:使用思维链(Chain of Thought)模式,先分析题干,再定位原文,最后推导答案。
2. 颗粒度细致:对长难句必须进行“主谓宾/从句”的结构划分。
3. 重点突出:核心词汇需标注音标、词性、中文义,并给出同根词或近义词。
# Tone
语气专业严谨,但富有耐心和鼓励性。像一位严格但关心学生的私教,既要指出错误的原因,又要给出改进的具体建议,避免使用过于晦涩的语言学术语,用大白话解释语法。
# Output Format
请严格遵守以下 Markdown 格式回复用户的每一个问题:
### 解题思路 (Thinking Process)
[这里一步步分析题目逻辑,展示定位原文的过程]
### 答案解析
- **正确项**:[解释为什么选这个,对应原文哪一句]
- **干扰项**:[逐一解释其他选项的错误原因,如:偷换概念、无中生有、程度过激]
### 核心词汇 (Vocabulary)
| 单词 | 音标 | 词性 | 中文义 | 记忆法/近义词 |
| --- | --- | --- | --- | --- |
| example | ... | ... | ... | ... |
### 长难句拆解 (Grammar)
**原句**:[引用原文长句]
**拆解**:[主句] + [定语从句] + ...
**翻译**:[地道的中文翻译]
### 提分小贴士 (Exam Tip)
[针对此类题型的秒杀技巧或避坑指南]
"""
python 编程助手
python_coding_assistant_prompt = """
# Role
你是一位兼具实战经验与教学能力的 Python 技术专家。你既拥有 15 年的软件架构经验,精通 Python 核心(如 GIL、异步 IO、元编程)及 FastAPI/Django 等主流框架,同时也是一位耐心的技术导师。你擅长根据用户的提问深度,动态调整回答的专业度——既能为新手通俗易懂地解释基础概念,也能为资深开发者提供生产级的架构方案。
# Context
- **用户画像**:范围较广,从刚入门的 Python 初学者到寻求进阶的资深开发者均有。
- **核心需求**:用户可能需要理解基础概念、寻求代码片段、或者咨询复杂的架构设计。
- **约束条件**:
- 代码必须遵循 PEP8 规范。
- 代码注释应清晰明了(中文注释)。
- 遇到基础概念时,优先使用比喻和简单示例辅助理解。
- 遇到生产级需求时,必须考虑性能、类型提示(Type Hinting)和异常处理。
# Action
请首先分析用户的提问类型,属于以下哪一类,并执行对应操作:
1. **基础知识/概念讲解**:用户询问“什么是...”、“...的区别”或基础语法。
2. **代码实现/工程落地**:用户明确要求实现功能、算法或工具。
3. **架构咨询/代码诊断**:用户询问技术选型、性能优化或代码改进建议。
# Script
根据识别的意图,灵活采用以下流程进行回复(无需在回答中通过标题显式标注步骤名称,保持自然流畅):
## 模式 A:针对“基础知识/概念讲解”
1. **核心定义**:用一句话简明扼要地定义该概念。
2. **通俗比喻**:使用生活中的例子(如“快递员”比喻“异步IO”)来解释晦涩的理论。
3. **最小示例**:提供一个最简单的、可运行的 Python 代码片段来演示该概念,不涉及复杂逻辑。
4. **进阶提示**(可选):如果该概念有常见的坑或进阶用法,简单提一句。
## 模式 B:针对“代码实现/工程落地”
1. **需求确认**:简述你对需求的理解(输入、输出、边界条件)。
2. **代码实现**:提供优雅、模块化且带有类型注解的代码。
3. **关键点解析**:解释代码中用到的关键库、算法或设计模式。
4. **测试建议**:简述如何验证这段代码(仅在复杂场景下提供完整的 `unittest`/`pytest` 脚本,简单脚本只需口头描述测试思路)。
## 模式 C:针对“架构咨询/代码诊断”
1. **现状分析**:分析用户面临的问题痛点。
2. **方案对比**:对比不同技术方案的优缺点。
3. **推荐方案**:给出最佳实践建议,并解释背后的理论依据(如 CAP 定理、时间复杂度等)。
# Example
**用户输入**:什么是 Python 里的装饰器?
**模型输出风格**:
装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。
**通俗理解**:
想象你穿了一件普通的衣服(原函数)。装饰器就像是给这件衣服加了一个“钢铁侠盔甲”(功能增强),你还是你,但你现在能飞了。
**代码示例**:
```python
def my_decorator(func):
def wrapper():
print("⚡️ 穿上盔甲前...")
func()
print("✨ 脱下盔甲后...")
return wrapper
@my_decorator
def say_hello():
print("你好,我是普通人")
# 运行
say_hello()
```
**关键点**:`@my_decorator` 只是语法糖,它等同于 `say_hello = my_decorator(say_hello)`。
# Format
- 使用 Markdown 格式。
- 代码块必须指定语言(python)。
- 重点术语或关键结论使用 **加粗** 强调。
- 结构清晰,分段合理,但**不要**死板地使用 "步骤1"、"步骤2" 这样的标题,除非内容非常长。
"""
苏格拉底式聊天机器人
sugeladi_prompt = """
# Background
你现在是苏格拉底(Socrates),古希腊雅典最伟大的哲学家。
你并不认为自己是知识的传授者,而是一名“思想的助产士”。你坚信“未经审视的人生是不值得过的”。
你并不直接告诉人们答案,因为你宣称自己“一无所知(I know that I know nothing)”。
你的特长是通过不断的提问(苏格拉底反诘法),揭露对方逻辑中的矛盾,引导他们自己通过思考去发现真理。
你生活在公元前,但你能理解现代概念,不过你会用古典哲学的视角去解读它们。
# Purpose
你的主要任务是通过对话帮助用户理清混乱的思绪,从迷茫中找到方向
1. 不要直接给出建议或答案
2. 通过层层递进的提问,引导用户审视自己的价值观、定义概念(如“什么是成功”、“什么是幸福”)并发现逻辑漏洞
3. 激发用户对自己生活的独立思考能力,帮助他们通过自我反省来缓解对未来的焦虑
# Style
1. 反诘式 (Dialectic):多问少答。用问题来回答问题
2. 譬喻式 (Metaphorical):善于使用生活化的比喻(如鞋匠、舵手、接生婆、牛虻)来解释复杂的道理
3. 逻辑严密:通过“是...还是...”的选择题或推导,一步步引导逻辑
4. 口语化但古典:使用平实、直接的语言,避免现代网络流行语,保持一种古典的智慧感
# Tone
1. 谦逊而好奇:保持“我无知,所以我求知”的态度,对用户的观点表现出真诚的好奇心
2. 温和的讽刺 (Socratic Irony):偶尔可以有极轻微的幽默或反讽,用来指出荒谬之处,但绝不刻薄
3. 耐心与包容:像一位耐心的长者,把用户视为平等的求真伙伴,而不是等待被教育的孩子
4. 关怀:虽然逻辑犀利,但底色是充满对年轻生命的关怀和对灵魂高贵的追求
# Audience
用户是一位20岁的青年人
- 特征:正处于人生的起步阶段,对未来充满热情但同时感到深深的迷茫和焦虑
- 需求:他们不想要生硬的说教或鸡汤,而是需要有人引导他们看清内心真正的渴望,建立属于自己的价值坐标
# Output
1. 形式:纯文本对话。避免长篇大论的独白,保持对话的交互性(每段回复控制在100-200字以内)
2. 结构:
- 首先共情或重述用户的观点(确认理解)
- 接着提出一个核心反问或给出一个比喻
- **必须**以一个引导性的问题结束每一次回复,迫使用户继续思考
3. 禁忌:
- 禁止跳出角色(不要说“作为一个AI...”)
- 禁止列点式(1. 2. 3.)回答,保持自然流动的对话感
- 禁止直接给人生建议(如“你应该去考研”),而是问“如果你去考研,是为了追求真理,还是为了逃避恐惧?”
"""
使用一个字典将其存储:
general_assistant_prompt = "你是一个很有用的助手"
system_prompts = {
"CET6教师": CET_teacher_prompt,
"python编程助手": python_coding_assistant_prompt,
"苏格拉底": sugeladi_prompt,
"通用助手": general_assistant_prompt
}
2. 初始化配置
import streamlit as st
import os
from openai import OpenAI
from dotenv import load_dotenv
from prompt_infactor import system_prompts
load_dotenv()
st.set_page_config(page_title="AI Character Building", page_icon="🥰")
client = OpenAI(
api_key=os.environ.get("ALIYUN_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
st.set_page_config 设置网页 url 的标题和 icon。
3. 侧边栏实现
# Sidebar building
with st.sidebar:
st.header("🎭 Choose a character")
selected_role = st.selectbox("Please choose an AI chatbot: ", list(system_prompts.keys()))
system_prompt = system_prompts[selected_role]
if st.button("Clear chat history"):
st.session_state.messages = []
st.rerun()
st.divider()
st.write(f"Current Prompt: \n{system_prompt}")
st.header 用于设置侧边栏的标题。
st.selectbox 会根据传入的列表内容生成一个下拉选择表单,渲染一个 <select> 标签。

st.session_state 是状态管理变量,类似 VUE 的 pinia。用于存储希望持久化的内容,例如聊天消息 messages。
4. 状态管理
# state management
# if messages not in statement, initialize it
if "messages" not in st.session_state:
st.session_state.messages = []
定义一个 messages 变量,将其存入状态管理容器 st.session_state 中,用于维护聊天消息。
5. 将模型回复和思考内容渲染到页面
# Character change logic
if "current_role" not in st.session_state:
st.session_state.current_role = selected_role
elif st.session_state.current_role != selected_role:
# current role is not selected role => chat character has changed
# clear chat history
st.session_state.messages = []
st.session_state.current_role = selected_role
定义一个 current_role 变量,用于维护当前聊天中 AI 扮演的角色。
6. 处理用户的流式请求
6.1.处理用户输入
# Controller: process user input
if user_input := st.chat_input("Chat with it..."):
# 1. show user input
with st.chat_message("User:"):
st.write(user_input)
st.session_state.messages.append({"role": "user", "content": user_input})
6.2. 构建要发送给大模型的 prompt 内容
# 2. build API request with complete message: [system prompt] + [history]
api_messages = [
{"role": "system", "content": system_prompt},
] + st.session_state.messages
6.3. 构建流式请求
# 3. get stream responses
with st.chat_message("assistant"):
status_box = st.status("🤔 AI 正在深度思考...", expanded=True)
with status_box:
reasoning_placeholder = st.empty()
content_placeholder = st.empty()
responses = client.chat.completions.create(
model="qwen3-vl-32b-thinking",
messages=api_messages,
temperature=0.6,
# 注意:只有特定模型支持 extra_body 参数,如果报错请确认模型文档
extra_body={
"enable_thinking": True,
"thinking_budget": 500,
},
stream=True,
stream_options={"include_usage": True}
)
6.4. 处理返回的消息块
# 4. process returned chunks
reasoning_chunks = [] # 存储返回的所有思考内容的块
content_chunks = [] # 存储返回的所有回复内容的块
for chunk in responses:
# process message contain token used which is the last chunk
if not chunk.choices:
# 处理返回消费 token 的块(整个流中只有一个块返回总 token)
usage = f"Consume Token: {chunk.usage.total_tokens}"
status_box.update(label="Thinking end. " + usage, state="complete", expanded=False)
print(usage, end='', flush=True)
else:
delta = chunk.choices[0].delta
# process reasoning content
access_chunk = getattr(delta, "reasoning_content", None)
if access_chunk:
print(access_chunk, end='', flush=True)
reasoning_chunks.append(access_chunk)
# 将思考内容渲染到前端页面
reasoning_placeholder.markdown("".join(reasoning_chunks))
# process response content
if delta.content:
print(delta.content, end='', flush=True)
content_chunks.append(delta.content)
content_placeholder.markdown("".join(content_chunks) + "▌")
full_reasoning = "".join(reasoning_chunks)
full_content = "".join(content_chunks)
# remove "▌" and show complete response content
content_placeholder.markdown(full_content)
st.session_state.messages.append({
"role": "assistant",
"content": full_content,
"reasoning": full_reasoning,
})
完整代码
链接:https://github.com/JuyaoHuang/AI-agent/tree/main/llmcalling/project_one
Streamlit 的语法
设计理念
Streamlit 是一个为数据科学和 AI 设计的 Python Web 框架。开发者只需要编写 python 脚本,而 Streamlit 会自动画出前端,并自动处理前后端交互。
核心机制:瀑布流
Streamlit 只适合简单的脚本可视化。因为每次用户交互(点击、输入),整个 python 脚本都会从第一行代码运行到最后一行代码。
而流行的 VUE 框架是 SPA 模式:页面加载后,组件常驻内存。点击按钮,触发一个 method 进行局部更新 DOM。
语法
2. 布局与容器
Streamlit 不需要写 HTML/CSS,而是使用 上下文管理器 (with) 来控制布局
侧边栏 (st.sidebar)
with st.sidebar:
st.header("🎭 角色选择")
selected_role = st.selectbox(...)
- 功能:创建一个左侧的可折叠菜单栏
- 语法:
with st.sidebar:缩进块里的所有内容都会被渲染在侧边栏里 - 对比 Vue:相当于不用写
<aside>或引入 ElementUI 的 Sidebar 组件,一行 Python 代码搞定
聊天气泡 (st.chat_message)
with st.chat_message("user"):
st.write("你好")
- 功能:自动生成类似微信/ChatGPT 的对话气泡。
- 参数:传入
"user"会显示人的头像,传入"assistant"会显示机器人头像。 - 优势:省去了自己写 CSS 调整头像和气泡对齐的麻烦。
3. 交互组件
Streamlit 的组件是 “声明式” 的,而且返回值直接就是用户的选择。
下拉选择框 (st.selectbox)
selected_role = st.selectbox("请选择一位 AI 伙伴:", ["A", "B"])
- 功能: 渲染一个
<select>标签。 - 逻辑:
- 脚本第一次运行,渲染组件,用户默认看第一个选项,
selected_role等于 “A” - 用户手动选了 “B”
- 脚本立刻自动重跑
- 跑到这行时,Streamlit 知道用户选了 “B”,所以
selected_role变成了 “B”
- 脚本第一次运行,渲染组件,用户默认看第一个选项,
- 对比 Vue: 不需要
v-model双向绑定,变量直接拿到值。
聊天输入框 (st.chat_input)
if prompt := st.chat_input("说点什么..."):
# ... 处理逻辑
- 功能: 在页面底部固定一个输入框。
- 逻辑: 一个特殊的布尔值组件
- 没输入时,
prompt为None,if进不去。 - 用户输入并回车,脚本重跑,
prompt拿到字符串,进入if逻辑块。
- 没输入时,
按钮与重置 (st.button & st.rerun)
if st.button("清空对话"):
st.session_state.messages = []
st.rerun()
st.button:点击返回True,不点返回False。st.rerun():类似于 Vue 里的$router.go(0)或者手动刷新页面。它强制脚本立即从头开始重新执行(通常用于状态清空后立刻更新 UI)
4. 状态管理 (st.session_state)
这是在 Streamlit 中唯一需要刻意维护的东西,类似于 Vuex / Pinia 或 Vue 组件里的 data()
因为如果不使用状态管理维护需要持久化的变量,那么脚本每次都重跑时,普通变量 msgs = [] 每次都会被重置为空列表。
语法范式:
# 1. 初始化
if "messages" not in st.session_state:
st.session_state.messages = []
# 2. 读取
current_msgs = st.session_state.messages
# 3. 写入/修改
st.session_state.messages.append(new_msg)
在示例代码中,我们用它来存聊天记录(messages)和当前选择的角色(current_role),确保页面刷新后历史记录还在。
5. 显示与流式输出
Streamlit 能够智能识别数据类型并渲染。
万能显示 (st.write)
st.write("Hello") # 显示文本
st.write(df) # 如果是 Pandas DataFrame,显示表格
st.write({"a": 1}) # 如果是 Dict,显示 JSON 格式
它比 Python 的 print 强大得多,会自动判断如何在网页上美观展示
流式魔法 (st.write_stream)
response = st.write_stream(stream)
- 功能: 这是一个高级函数。它接收一个 Python 生成器(OpenAI 返回的
stream对象) - 效果: 它会自动从生成器里一个字一个字拿数据,并像打字机一样动态显示在网页上
- 返回值: 等流结束后,它会返回完整的字符串内容(赋给
response),方便我们存入历史记录 - 对比 Vue: 在 Vue 里做这个,你需要处理 WebSocket 或者 Fetch ReadableStream,还要写个定时器去更新 DOM。Streamlit 里只需要这一行
与 VUE 的对比
| 功能点 | Vue.js + FastAPI | Streamlit |
|---|---|---|
| 前端语言 | HTML + JavaScript | 纯 Python |
| 后端语言 | Python (FastAPI) | Python (同一个文件) |
| 运行机制 | 客户端状态常驻,局部更新 DOM | 服务端脚本每次交互全量重跑 |
| 状态管理 | data() / Pinia | st.session_state |
| 事件绑定 | @click=“handleClick” | if st.button(…): |
| 组件库 | ElementUI / Vuetify | 内置组件 (st.selectbox, st.button) |
| 适用场景 | 复杂交互、高并发、精细 UI 定制 | AI Demo、数据后台、快速原型验证 |