Skip to content

项目目标 ​

做一个"AI 智能伴侣"网页:左边是控制面板(会话列表、伴侣昵称、性格),右边是聊天窗口,输入问题后大模型以伴侣的口吻流式回复。

做完整个章节后,页面长这样:

AI 智能伴侣最终效果:左侧为会话管理面板,右侧为聊天窗口

本篇是第一步:把基本交互跑通(对应课程代码 03. ai_partner_1.py)。后续依次叠加流式输出、会话记忆、会话管理。

整体骨架 ​

text
1. 设置页面配置      st.set_page_config
2. 展示标题和 Logo   st.title / st.logo
3. 初始化会话状态    st.session_state
4. 渲染历史消息      for + st.chat_message
5. 创建大模型客户端  OpenAI(...)
6. 等待用户输入      st.chat_input
7. 调用大模型并展示  client.chat.completions.create
8. 把这一轮对话存入 session_state

完整代码(基本交互版) ​

python
import streamlit as st
import os
from openai import OpenAI

print("----------> 重新执行此文件 , 渲染展示页面")

# 设置页面的配置项
st.set_page_config(
    page_title="AI智能伴侣",
    page_icon="🤖",
    layout="wide",
    initial_sidebar_state="expanded",
    menu_items={}
)

# 大标题
st.title("AI智能伴侣")

# Logo
st.logo("resources/logo.png")

# 系统提示词
system_prompt = "你是一名非常可爱的AI助理, 你的名字叫小甜甜, 请你使用温柔可爱的语气回答用户的问题"

# 初始化聊天信息
if "messages" not in st.session_state:
    st.session_state.messages = []

# 展示聊天信息
for message in st.session_state.messages:  # {"role": "user", "content": prompt}
    st.chat_message(message["role"]).write(message["content"])

# 创建与AI大模型交互的客户端对象 (DEEPSEEK_API_KEY 环境变量的名字, 值就是DeepSeek的API_KEY的)
client = OpenAI(api_key=os.environ.get('DEEPSEEK_API_KEY'), base_url="https://api.deepseek.com")

# 消息输入框
prompt = st.chat_input("请输入您要问的问题")
if prompt:  # 字符串会自动转换为布尔值, 如果字符串非空, 则为True; ""否则为False
    st.chat_message("user").write(prompt)
    print("----------> 调用AI大模型, 提示词: ", prompt)

    # 保存用户输入的提示词
    st.session_state.messages.append({"role": "user", "content": prompt})

    # 调用AI大模型
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": prompt},
        ],
        stream=False
    )

    # 输出大模型返回的结果
    print("<---------- 大模型返回的结果: ", response.choices[0].message.content)
    st.chat_message("assistant").write(response.choices[0].message.content)

    # 保存大模型返回的结果
    st.session_state.messages.append({"role": "assistant", "content": response.choices[0].message.content})

关键点拆解 ​

1. 为什么第一句是 print 调试 ​

python
print("----------> 重新执行此文件 , 渲染展示页面")

因为 Streamlit 每次交互都会重新执行整个脚本(见 Streamlit入门)。这行 print 打在终端里,用来亲眼确认"脚本又跑了一遍"——理解这一点,才能理解后面为什么必须用 st.session_state。

2. 用 session_state 保存聊天记录 ​

python
if "messages" not in st.session_state:
    st.session_state.messages = []
写法含义
if "messages" not in st.session_state只在第一次运行时初始化(字典里没有这个 key 才创建空列表)
以后每次重跑条件不成立,直接沿用已有的列表,历史消息不会丢

IMPORTANT

不能写成 st.session_state.messages = [](不加 if 判断)。那样每次重跑都会把聊天记录清空,界面会一直只剩当前这一句。

3. 渲染历史消息 ​

python
for message in st.session_state.messages:
    st.chat_message(message["role"]).write(message["content"])

messages 列表里的每个元素都带 role,直接把它传给 st.chat_message(),user 显示在右侧、assistant 显示在左侧,样式自动区分。

NOTE

这个 messages 列表的格式({"role": ..., "content": ...})就是发给大模型 API 的格式,界面展示和接口调用用的是同一份数据,不需要单独维护两套。

4. 输入的判断 ​

python
if prompt:

st.chat_input() 在用户还没输入内容时返回 None,输入并回车后返回字符串。字符串在 if 中会自动转成布尔值:非空为 True,"" 为 False。所以 if prompt: 等价于"用户这次真的发了消息"。

5. 这一轮调用为什么只发了两个 message ​

python
messages=[
    {"role": "system", "content": system_prompt},
    {"role": "user", "content": prompt},
]

这一版只把当前这一句发给模型,所以此时 AI 还记不住上一轮(模型是无状态的)。下一步就要改造成"带上历史消息",见 会话记忆。

相关 ​

练习题 ​

一、知识回顾(读完直接做下面的实践题) ​

  1. 页面配置、大标题、Logo 分别是:st.set_page_config / st.title / st.logo
  2. 初始化聊天记录:if "messages" not in st.session_state: st.session_state.messages = []
  3. 渲染历史消息:for message in st.session_state.messages: st.chat_message(message["role"]).write(message["content"])
  4. 底部聊天输入框:prompt = st.chat_input("请输入您要问的问题")
  5. if prompt: 能判断"用户这次真的发了消息",是因为非空字符串转布尔值是 True
  6. 存下用户的问题:st.session_state.messages.append({"role": "user", "content": prompt})
  7. 取模型回复:response.choices[0].message.content
  8. 为什么必须用 st.session_state?因为 Streamlit 每次交互都会重新执行整个脚本,普通变量会丢

二、裸写题 ​

  • - 提示(先自己想,实在想不出再点开)

    一级 · 思路:页面每次重跑都要能"看到"历史 → 把消息列表存进会话状态;渲染就是遍历这个列表 二级 · 方法:st.session_state.messages / st.chat_message / st.chat_input三级 · 骨架:for message in ...: st.chat_message(message["role"]).write(message["content"])

    - 参考答案(做完再点开)

    python
    import streamlit as st
    
    # 先塞两条假消息,检验"渲染 + 追加"这条链路
    if "messages" not in st.session_state:
        st.session_state.messages = [
            {"role": "user", "content": "你好"},
            {"role": "assistant", "content": "你好呀,我是小甜甜~"},
        ]
    
    st.title("AI智能伴侣(暂不接模型)")
    
    # 每次重跑都按列表渲染一遍历史
    for message in st.session_state.messages:
        st.chat_message(message["role"]).write(message["content"])
    
    prompt = st.chat_input("请输入您要问的问题")
    if prompt:
        st.session_state.messages.append({"role": "user", "content": prompt})
        st.chat_message("user").write(prompt)
    
    # 刷新页面后消息还在 —— 因为列表存在 session_state 里,不是普通变量
  • - 提示(先自己想,实在想不出再点开)

    一级 · 思路:存 → 调 → 显示 → 再存,四步都要在 if prompt: 里面 二级 · 方法:client.chat.completions.create / st.session_state.messages.append三级 · 骨架:messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": prompt}](这一版只发当前这一句,看看 AI 记不记得上一轮)

    - 参考答案(做完再点开)

    python
    import os
    import streamlit as st
    from openai import OpenAI
    
    st.set_page_config(page_title="AI智能伴侣", page_icon="🤖", layout="wide")
    st.title("AI智能伴侣")
    
    system_prompt = "你是一名非常可爱的AI助理, 你的名字叫小甜甜, 请你使用温柔可爱的语气回答用户的问题"
    
    if "messages" not in st.session_state:
        st.session_state.messages = []
    
    for message in st.session_state.messages:
        st.chat_message(message["role"]).write(message["content"])
    
    client = OpenAI(api_key=os.environ.get("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com")
    
    prompt = st.chat_input("请输入您要问的问题")
    if prompt:
        st.chat_message("user").write(prompt)
        st.session_state.messages.append({"role": "user", "content": prompt})
    
        response = client.chat.completions.create(
            model="deepseek-chat",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": prompt},
            ],
            stream=False,
        )
        reply = response.choices[0].message.content
        st.chat_message("assistant").write(reply)
        st.session_state.messages.append({"role": "assistant", "content": reply})