项目一要求

构建一个多功能角色聊天机器人

  • 目标:熟练使用OpenAI API和Prompt Engineering
  • 任务:创建一个简单的Web界面(用Streamlit),用户可以选择不同的角色:“Python编程助手”、“英语口语教练”、“苏格拉底”,程序会根据选择,使用不同的System Prompt与用户进行高质量对话
  • 收获:精通API调用、掌握核心的提示工程技巧

代码实现

本项目较为简单,故直接采用 Streamlit 框架实现一个简单的前端页面进行展示。

本项目涉及到的 Streamlit 语法都会在后文讲述。当然代码实现过程也有相应的一些解释,方便理解

1

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> 标签。

2

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> 标签。
  • 逻辑:
    1. 脚本第一次运行,渲染组件,用户默认看第一个选项,selected_role 等于 “A”
    2. 用户手动选了 “B”
    3. 脚本立刻自动重跑
    4. 跑到这行时,Streamlit 知道用户选了 “B”,所以 selected_role 变成了 “B”
  • 对比 Vue: 不需要 v-model 双向绑定,变量直接拿到值。

聊天输入框 (st.chat_input)

if prompt := st.chat_input("说点什么..."):
    # ... 处理逻辑
  • 功能: 在页面底部固定一个输入框。
  • 逻辑: 一个特殊的布尔值组件
    • 没输入时,promptNoneif 进不去。
    • 用户输入并回车,脚本重跑,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 + FastAPIStreamlit
前端语言HTML + JavaScript纯 Python
后端语言Python (FastAPI)Python (同一个文件)
运行机制客户端状态常驻,局部更新 DOM服务端脚本每次交互全量重跑
状态管理data() / Piniast.session_state
事件绑定@click=“handleClick”if st.button(…):
组件库ElementUI / Vuetify内置组件 (st.selectbox, st.button)
适用场景复杂交互、高并发、精细 UI 定制AI Demo、数据后台、快速原型验证
Author

JuyaoHuang

Publish Date

12 - 09 - 2025