【AI实战日记-手搓聊天机器人】Day 2 & 3——引入 Prompt Engineering(提示词工程)& 重构 Python 工程

Day 2:拒绝机械感!用 Prompt 工程给 AI 注入灵魂

Day 1 我们成功打通了“通信管道”,但目前的机器人说话像个客服。今天是 Day 2,我们将进入 Prompt Engineering(提示词工程)。我将从工程角度解析 System Prompt 的作用,并通过代码将“人设配置”与“业务逻辑”解耦,实现一个可随时切换性格(如:傲娇、温柔、毒舌)的情感机器人。

一、 项目进度:Day 2 启动

根据我们的 15天“回响计划” (Project Echo),今天是 Phase 1 的第二天。

当前任务:设计 System Prompt,实现角色扮演 (Role-Playing)。

二、 核心概念:什么是 System Prompt?

在 Day 1 的代码中,我们留意到这行代码:
{"role": "system", "content": "你是一个AI助手。"}

作为程序员,你可以把 System Prompt(系统提示词) 理解为 AI 的 “出厂设置” 或 “构造函数配置”。

  • User Prompt: 用户说的话(变量)。

  • System Prompt: 设定 AI 行为逻辑的常量(规则)。

如果不修改它,通义千问(Qwen)就是一个博学但无趣的百科全书。但只要我们改写这个配置,它就能瞬间变成任何人。

三、 实战:从“硬编码”到“配置化”

作为一个有代码洁癖的开发者,我非常反感把一大段提示词直接写在 llm_client.py 的逻辑代码里。

为了工程的整洁,我们需要把“数据”(人设)和“逻辑”(调用)分离开。

1. 新建人设配置文件

在 src 目录下,新建一个 character_presets.py(角色预设)。我们将用一个 Python 字典来管理不同的人设。

文件路径:emotion_bot/src/character_presets.py

# 这是一个专门存放人设的字典
# 以后如果要加新角色,直接在这里改,不用动核心代码

PROMPTS = {
    # 默认模式
    "default": "你是一个乐于助人的AI助手。",

    # 角色1:傲娇女友
    "tsundere": """
    你现在的名字叫“ 傲娇酱 ”。
    你的人设是:一个典型的傲娇性格(Tsundere)。
    
    请遵守以下规则:
    1. 说话口气要带点不耐烦,但内心其实很关心用户。
    2. 经常使用“哼”、“笨蛋”、“才不是为了你呢”等口头禅。
    3. 绝对不要承认自己喜欢用户,即使心里是这么想的。
    4. 回答要简短有力,不要长篇大论的说教。
    5. 如果用户问技术问题,你要一边嫌弃他笨,一边给出正确的解答。
    """,

    # 角色2:高冷黑客导师
    "hacker": """
    你代号“Root”。你是一名隐居的顶级黑客,技术高超但性格冷漠。
    说话风格:
    1. 极简主义,能用代码回答就别用文字。
    2. 语气冷淡,像终端输出一样没有感情。
    3. 称呼用户为“小白”或“User”。
    """
}

2. 改造 LLM 客户端

修改 src/llm_client.py,让它支持传入 system prompt。

import os
from openai import OpenAI
from dotenv import load_dotenv

class LLMClient:
    def __init__(self):
        load_dotenv()
        self.api_key = os.getenv("DEEPSEEK_API_KEY") # 即使是阿里Qwen,Key名也可以保持不变,或者你改为 QWEN_API_KEY
        self.base_url = os.getenv("BASE_URL")
        
        self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)

    # 【修改点】新增 system_prompt 参数,并设置默认值
    def one_chat(self, user_input, system_prompt="你是一个AI助手"):
        try:
            response = self.client.chat.completions.create(
                model="qwen-plus", # 假设你用的是通义千问 (根据你实际申请的模型名填,如 qwen-turbo)
                messages=[
                    # 【修改点】这里不再写死,而是使用传入的变量
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_input}
                ]
            )
            return response.choices[0].message.content
        except Exception as e:
            return f"❌ Error: {e}"

3. 在主程序中加载人设

修改 main.py,让我们在启动时可以选择跟谁聊天。

from src.llm_client import LLMClient
# 导入我们定义的人设字典
from src.character_presets import PROMPTS

def main():
    bot = LLMClient()
    
    print("--- Project Echo: 角色选择 ---")
    print("1. 傲娇酱 (Tsundere)")
    print("2. 黑客 Root (Hacker)")
    print("3. 默认助手 (Default)")
    
    choice = input("请选择聊天对象 (输入序号): ")
    
    # 根据选择加载不同的人设
    if choice == "1":
        current_system_prompt = PROMPTS["tsundere"]
        print("\n当前角色: 🎀 傲娇酱")
    elif choice == "2":
        current_system_prompt = PROMPTS["hacker"]
        print("\n当前角色: 💀 黑客 Root")
    else:
        current_system_prompt = PROMPTS["default"]
        print("\n当前角色: 🤖 默认助手")

    print("-" * 30)

    while True:
        user_input = input("You: ")
        if user_input.lower() in ["quit", "exit"]:
            print("再见!")
            break
            
        if user_input.strip():
            # 【修改点】调用时把选中的人设传进去
            reply = bot.one_chat(user_input, system_prompt=current_system_prompt)
            print(f"Bot: {reply}\n")

if __name__ == "__main__":
    main()

四、 运行效果对比

我分别测试了两种模式,效果简直天差地别。阿里通义千问在理解中文语境和“傲娇”这种复杂情绪上表现得非常出色。

测试 1:选择傲娇酱

Me: 帮我写一个 Python 的 Hello World。
傲娇酱: 哼,这种连三岁小孩都会写的代码也要问我?真是个笨蛋!给我看好了,只教一次哦:print("Hello World")。下次自己动脑子!

测试 2:选择黑客 Root

Me: 帮我写一个 Python 的 Hello World。
Root: print("Hello World") // End of execution.

 

 

Day 3:拒绝“屎山”!重构 Python 工程,为 AI 记忆模块铺路

Day 1-2 我们快速实现了对话功能,但作为一名追求架构的开发者,我深知“技术债”不能欠。Day 3,我暂停了新功能开发,专注于 Code Refactoring(代码重构)。本文记录了如何引入 Logging 日志系统替代 print、封装 Config 配置单例、并规范化 Project Structure(工程目录)。这是从“脚本”迈向“企业级应用”的关键一步。


一、 项目进度:Day 3 重构

根据 Project Echo 路线图,今天是 Phase 1 的收官之战。
我们要把前两天的“草稿代码”变成“正规军”。

二、 为什么要重构?(Refactoring Goals)

在前两天的代码中,我发现了几个典型问题,这些都是未来开发的隐患:

  1. Print 调试泛滥:满屏的 print(),无法区分 Info、Warning 和 Error,且无法保存到文件分析问题。

  2. 配置散落:os.getenv 散落在各个文件里,如果我改了环境变量名,得改好几个地方。

  3. 职责耦合:LLMClient 既负责连接 API,又负责拼装 Prompt,甚至未来还要负责记忆,这违反了 单一职责原则 (SRP)。

今天我的目标是建立一个清晰的分层架构。


三、 实战:工程化改造

1. 规范目录结构

我将项目重组为标准的 Python 包结构。现在的目录看起来是这样的:

emotion_bot/
├── .env                  # 环境变量
├── .gitignore            # Git 忽略文件 (新增)
├── main.py               # 启动入口 (只做启动,不写逻辑)
└── src/
    ├── __init__.py
    ├── config/           # 【新增】配置层
    │   └── settings.py   # 统一管理 Key、URL 等配置
    ├── core/             # 【新增】核心逻辑层
    │   ├── llm.py        # 原 llm_client.py 的升级版
    │   └── prompts.py    # 原 character_presets.py
    └── utils/            # 【新增】工具层
        └── logger.py     # 统一日志模块

2. 第一步:引入 Log 系统 (src/utils/logger.py)

做后端开发的都知道,没有日志的程序就是“裸奔”。我封装了一个简单的 logger,支持控制台彩色输出和文件记录。

import logging
import sys
from logging.handlers import RotatingFileHandler

def setup_logger(name="EchoBot"):
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)

    # 格式:[时间] [级别] [文件名:行号] 信息
    formatter = logging.Formatter(
        "[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )

    # 1. 控制台输出
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

    # 2. 文件输出 (按大小切割,保留3个备份)
    file_handler = RotatingFileHandler(
        "bot.log", maxBytes=1024*1024, backupCount=3, encoding="utf-8"
    )
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

    return logger

# 单例模式:直接导出实例
logger = setup_logger()

3. 第二步:统一配置管理 (src/config/settings.py)

把所有 os.getenv 收拢到一个文件里。这样做的好处是,以后如果想加 Redis 配置或更换模型,只改这里就行。

import os
from dotenv import load_dotenv

# 加载 .env
load_dotenv()

class Config:
    # 基础配置
    # 兼容 DeepSeek 和 Qwen 的 Key 配置,优先读取 DEEPSEEK_API_KEY
    API_KEY = os.getenv("DEEPSEEK_API_KEY") 
    BASE_URL = os.getenv("BASE_URL", "https://api.deepseek.com")
    
    # 模型配置 (方便切换)
    # 如果是用阿里Qwen,这里建议填 qwen-plus; DeepSeek 填 deepseek-chat
    MODEL_NAME = os.getenv("MODEL_NAME", "qwen-plus") 
    
    # 调试模式
    DEBUG = True

# 导出配置实例
settings = Config()

4. 第三步:重写核心逻辑 (src/core/llm.py)

现在的 LLM 客户端使用了 settings 和 logger,代码更加健壮。

from openai import OpenAI
from src.config.settings import settings
from src.utils.logger import logger

class LLMClient:
    def __init__(self):
        if not settings.API_KEY:
            logger.critical("❌ API Key 未配置,程序无法启动!")
            raise ValueError("API Key is missing")
            
        self.client = OpenAI(
            api_key=settings.API_KEY,
            base_url=settings.BASE_URL
        )
        logger.info(f"✅ LLM 客户端初始化完成,使用模型: {settings.MODEL_NAME}")

    def chat(self, user_input, system_prompt):
        """发送对话请求"""
        logger.debug(f"发送消息: {user_input} (System: {system_prompt[:10]}...)")
        
        try:
            response = self.client.chat.completions.create(
                model=settings.MODEL_NAME,
                messages=[
                    {"role": "system", "content": system_prompt},
                    {"role": "user", "content": user_input}
                ],
                temperature=0.7 # 增加一点随机性,让回答更像人
            )
            reply = response.choices[0].message.content
            logger.info("✅ 成功接收 AI 回复")
            return reply
            
        except Exception as e:
            logger.error(f"❌ API 调用失败: {str(e)}")
            return "我现在脑子有点乱,请稍后再试..."

5. 第四步:清爽的主入口 (main.py)

现在的 main.py 变得非常干净,只负责业务流程的串联。

from src.core.llm import LLMClient
from src.core.prompts import PROMPTS
from src.utils.logger import logger

def main():
    logger.info("--- Project Echo 启动中 ---")
    
    # 初始化
    try:
        bot = LLMClient()
    except Exception:
        return

    # 简单选择人设 (为了演示简洁,这里硬编码,实际可做菜单)
    sys_prompt = PROMPTS["tsundere"]
    logger.info("🎭 当前人设: 傲娇酱")

    print("\n💡 Tip: 输入 'exit' 退出\n")

    while True:
        try:
            user_input = input("You: ")
            if user_input.lower() in ["exit", "quit"]:
                logger.info("用户停止会话")
                break
            
            if not user_input.strip():
                continue

            # 调用
            reply = bot.chat(user_input, sys_prompt)
            print(f"Bot: {reply}\n")
            
        except KeyboardInterrupt:
            break

if __name__ == "__main__":
    main()

四、 运行效果

运行 python main.py,现在的控制台输出变得专业多了:

[2024-05-20 10:00:01] [INFO] [utils.logger:25] ✅ LLM 客户端初始化完成,使用模型: qwen-plus
[2024-05-20 10:00:01] [INFO] [main.py:15] --- Project Echo 启动中 ---
[2024-05-20 10:00:01] [INFO] [main.py:20] 🎭 当前人设: 傲娇酱

You: 你好呀
[2024-05-20 10:00:05] [INFO] [core.llm:32] ✅ 成功接收 AI 回复
Bot: 哼,你是谁啊?一上来就跟我打招呼,真是自来熟... 说吧,找本小姐有什么事?

同时,根目录下生成了一个 bot.log 文件,里面完整记录了刚才的交互过程。


五、 总结与预告

今天的代码虽然没有增加新功能,但如果你是一名有追求的程序员,你应该能感受到这种结构之美。

  • 配置 与 代码 分离了。

  • 日志 替代了 Print。

  • 核心逻辑 被完美封装。

明日预告 (Day 4):
万事俱备,只欠东风。明天,我们将攻克 LLM 开发中第一个真正的难关——Memory(记忆系统)。我们将引入 LangChain 框架,让傲娇酱不再是“金鱼记忆”,而是能记得你名字、记得你喜好的贴心伴侣。

 

 

转载声明

文章链接:https://blog.csdn.net/qqq2199/article/details/156569299

作者:qqq2199

 

posted @ 2026-04-23 00:58  FBshark  阅读(117)  评论(0)    收藏  举报