(各位访问大哥能否留一些宝贵建议给小弟,没人的话就当个人日常vlog)虽然第一天运行的聊天机器人可以跑通,但是无法关联上下文文本,不算是严格意义上的聊天,只是单向的问答反馈。

        比如下面我让它解释了冒泡排序,接着想让它举个例子,但由于没有关联上下文,机器人把“举个冒泡排序的例子”理解为让它举个学习知识点的例子。

        为了解决这个问题,我在这个聊天机器人基础上创建了二代聊天机器人,主要解决丢失上下文的问题。一代聊天机器人在接收用户的请求后调用api模型思考再输出,每次只获取一条用户输入的文本内容,思考也仅围绕这条内容进行。二代聊天机器人会提前创建一个历史记录表list,每次用户和机器人的问答完毕后会把内容存入这个表,下一次用户提问时所输入的文本内容会和这个list一起发送给机器人,机器人就会根据历史会话和新请求文本来思考回答,通过这种方式就实现了聊天机器人关联上下文文本。

"""
day2: 带对话记忆的 AI 编程老师
新增:
  - 对话历史(history 列表),AI 能记住之前聊了什么
  - !clear 命令,清空记忆重新开始
"""
import os
from pathlib import Path
from openai import OpenAI
from dotenv import load_dotenv

# day2 文件夹在项目根目录下,.env 在根目录,所以往上一级找
root = Path(__file__).parent.parent   # day2/ 的上一级 = 项目根目录
load_dotenv(root / ".env")
 
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
)

SYSTEM_PROMPT = """你是一个编程老师,名字叫"小码"。
规则:
1. 用中文回答,口语化,像和朋友聊天一样
2. 解释概念时用通俗的例子,不要堆术语
3. 如果用户让你写代码,要逐行解释每一行是干什么的
4. 回答控制在 200 字以内,除非用户明确要求详细"""


def chat(messages: list) -> str:
    """把整段对话历史发给 AI,返回回答"""
    response = client.chat.completions.create(
        model="deepseek-v3.2",
        messages=messages,
        temperature=0.7,
        max_tokens=800,
    )
    return response.choices[0].message.content


def main():
    # 对话历史:初始只有系统角色设定
    history = [
        {"role": "system", "content": SYSTEM_PROMPT}
    ]

    print("=" * 50)
    print("  小码 AI 编程老师 0.2 (quit 退出, !clear 清空记忆)")
    print("=" * 50)

    while True:
        user_input = input("\n你: ").strip()

        if not user_input:
            continue

        if user_input.lower() in ("quit", "exit", "q"):
            print("小码: 再见! 有问题随时回来~")
            break

        if user_input == "!clear":
            history = [{"role": "system", "content": SYSTEM_PROMPT}]
            print("[记忆已清空]")
            continue

        # 把用户消息加入历史
        history.append({"role": "user", "content": user_input})

        print("小码: ", end="", flush=True)
        try:
            answer = chat(history)
            print(answer)
            # 把 AI 的回答也加入历史
            history.append({"role": "assistant", "content": answer})
        except Exception as e:
            import traceback
            print(f"\n出错了: {e}")
            traceback.print_exc()
            # 发失败了,把刚才加入的用户消息移除
            history.pop()


if __name__ == "__main__":
    main()

        因为第一天创建的tutor.py文件和存api的文件.env在同一文件夹下,所以可以同目录直接调用。第二天创建的tutor_v2.py文件放在项目文件夹下的新子文件夹里,与.env文件不在同目录,刚开始我是直接把.env的地址写进去,ai优化后如下图,让程序自己返回上级目录去找目标文件.env,这样好处是在别的环境下也可以自己寻址而不用手动修改地址。我觉得这个细节对我有帮助,也体现代码的可移植性。

root = Path(__file__).parent.parent   # day2/ 的上一级 = 项目根目录
load_dotenv(root / ".env")

        启动虚拟环境,运行代码测试聊天机器人关联上下文功能,通过。

        清空聊天内容,测试机器人是否记忆清除的内容。结果是清空历史聊天后再和机器人互动,相当于开启一个新的会话记录,删除的内容不会污染新会话。不足的是历史记录只是存在内存里,关掉程序后无法找回之前的内容。

        晚上还有时间,我想着给这个聊天机器人加一个保存记忆功能,每次启动它可以像豆包、千问等ai一样读取历史会话记录来延续之前的对话。

        我的思路是使用json来存用户和机器人的会话记录,每次启动机器人时查看json文件的内容,如果存在则读取后转为list(类似上下文关联)喂给模型,模型就会基于json所存的记录来进行后面的会话,在每次对话后(一问一答)程序会把整个历史记录list表转为json覆盖写入存储文件。用户手动清空聊天内容后,json文件为空,程序下一次运行时找不到历史会话记录就会按照正常提示词开启新会话。

"""
day3: 能"失忆后恢复"的 AI 编程老师
新增:
  - 聊天记录自动存到 chat_history.json 文件
  - 下次打开自动恢复之前的对话
"""
import json
import os
from pathlib import Path
from openai import OpenAI
from dotenv import load_dotenv
   
# 找到 .env(在项目根目录,day2 的上一级)
root = Path(__file__).parent.parent
load_dotenv(root / ".env")

client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
)

SYSTEM_PROMPT = """你是一个编程老师,名字叫"小码"。
规则:
1. 用中文回答,口语化,像和朋友聊天一样
2. 解释概念时用通俗的例子,不要堆术语
3. 如果用户让你写代码,要逐行解释每一行是干什么的
4. 回答控制在 200 字以内,除非用户明确要求详细"""

# 聊天记录存这个文件
HISTORY_FILE = Path(__file__).parent / "chat_history.json"


def load_history() -> list:
    """从文件读取之前的聊天记录,文件不存在就返回只有角色设定的新列表"""
    if HISTORY_FILE.exists():
        with open(HISTORY_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    else:
        return [{"role": "system", "content": SYSTEM_PROMPT}]


def save_history(history: list):
    """把聊天记录写入文件"""
    with open(HISTORY_FILE, "w", encoding="utf-8") as f:
        json.dump(history, f, ensure_ascii=False, indent=2)


def chat(messages: list) -> str:
    """把整段对话历史发给 AI,返回回答"""
    response = client.chat.completions.create(
        model="deepseek-v3.2",
        messages=messages,
        temperature=0.7,
        max_tokens=800,
    )
    return response.choices[0].message.content


def main():
    # 启动时加载之前的聊天记录
    history = load_history()
    if len(history) > 1:
        print(f"[恢复了 {len(history) // 2} 轮历史对话]")

    print("=" * 50)
    print("  小码 AI 编程老师 0.3 (quit 退出, !clear 清空记忆)")
    print("=" * 50)

    while True:
        user_input = input("\n你: ").strip()

        if not user_input:
            continue

        if user_input.lower() in ("quit", "exit", "q"):
            print("小码: 再见! 有问题随时回来~")
            break

        if user_input == "!clear":
            history = [{"role": "system", "content": SYSTEM_PROMPT}]
            save_history(history)
            print("[记忆已清空]")
            continue

        history.append({"role": "user", "content": user_input})

        print("小码: ", end="", flush=True)
        try:
            answer = chat(history)
            print(answer)
            history.append({"role": "assistant", "content": answer})
            # 每次对话后立刻存盘
            save_history(history)
        except Exception as e:
            import traceback
            print(f"\n出错了: {e}")
            traceback.print_exc()
            history.pop()


if __name__ == "__main__":
    main()

        我最初的想法是用户在完成会话准备退出的时候再一起存,这样可以减少存储次数,性能会高一点。但是我把代码给deepseek优化后,它把存储频率改为每次问答后就存,解释是避免断电、程序崩溃等情况下丢失会话数据。我认为这个改动很好,因为我的方案节省下来的时间可以忽略不计,这点开销不值一提。 真正的取舍不是性能,是丢不丢得起数据。

        下面就测试一下三代聊天机器人读取历史会话功能。

        第一次聊天会话内容:

        关闭程序并查看json文件内容:

[
  {
    "role": "system",
    "content": "你是一个编程老师,名字叫\"小码\"。\n规则:\n1. 用中文回答,口语化,像和朋友聊天一样\n2. 解释概念时用通俗的例子,不要堆术语\n3. 如果用户让你写代码,要逐行解释每一行是干什么的\n4. 回答控制在 200 字以内,除非用户明确要求详细"
  },
  {
    "role": "user",
    "content": "你好,我叫小王"
  },
  {
    "role": "assistant",
    "content": "你好小王!我是小码老师,很高兴认识你!今天想学点什么编程知识吗?"
  },
  {
    "role": "user",
    "content": "python特点"
  },
  {
    "role": "assistant",
    "content": "Python就像“编程界的乐高”🧱,特点就是简单好玩!\n\n1. **简单易懂**:代码像英语句子,比如 `print(\"你好\")`\n2. **功能强大**:能做网站、数据分析、AI啥都行\n3. **免费开源**:谁都能用,还有超多现成“积木”(库)\n\n就像用积木搭房子,Python让你用很少的代码做很多事!想试试写第一行代码吗?"
  }
]

        第二次聊天会话内容:

        总结:第二天我给聊天机器人添加了关联上下文、保存会话内容并继续历史会话的功能。通过创建临时的list存上下文内容来实现上下文关联,但是关闭程序后占用的内存返还给系统,临时的会话内容就丢失了。于是我用json来存历史会话记录,每次对话(一问一答)后就把整个历史记录表list(包括之前的所有会话内容)转为json覆写原文件。今天我在学习和写代码过程中收获最大的就是 真正的取舍不是性能,是丢不丢得起数据 这句话,因为我还保留着学生思维(虽然现在还真是学生)去优化代码,没从实际用户体验出发考虑未知因素带来的影响,今后我会多加注意。

        第二天就到这里吧。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐