乐于分享
好东西不私藏

API实战:搭建自己的AI助手

API实战:搭建自己的AI助手

这是我的AI学习日记第8篇。上一篇学会了API调用,这篇来做个真正有用的东西——一个完整的AI助手。

先说结论

学会API调用只是第一步。
真正有价值的是:把AI能力封装成一个好用的工具
这篇我会分享如何搭建一个AI助手,具备以下功能:
功能
说明
多轮对话
记住上下文,连续交流
角色切换
一键切换不同角色(翻译、代码审查、文档助手)
历史管理
保存对话历史,随时查看
错误处理
网络问题、超时、余额不足

为什么需要一个AI助手

有人会问:网页版AI不就够用了吗?
我的答案是:不够用
原因:
  1. 网页版功能有限:不能定制角色、不能保存历史、不能批量处理
  2. 工作流不连贯:切换窗口、复制粘贴,打断思路
  3. 敏感数据问题:有些代码和数据不适合发给网页版
自己搭建AI助手的好处
  1. 完全定制:想要什么功能就加什么
  2. 数据安全:敏感数据可以本地处理
  3. 自动化:可以集成到工作流里

整体设计

架构图

模块划分

模块
职责
角色管理器
管理不同的角色Prompt
API调用器
调用DeepSeek API,处理错误
历史管理器
保存、读取、清理对话历史
配置管理
API Key、模型选择等

第一步:环境准备

安装依赖

pip install openai
说明
  • openai:OpenAI官方Python库,DeepSeek API兼容其接口格式
  • 需要 Python 3.9 及以上版本(使用了新的类型注解语法)

项目结构

ai-assistant/├── main.py           # 主程序入口├── config.py         # 配置管理├── roles.py          # 角色定义├── history.py        # 历史管理├── api_client.py     # API调用├── requirements.txt  # 依赖列表└── conversations/    # 对话历史存储    └── .gitkeep
requirements.txt
openai>=1.0.0
创建项目目录:
mkdir -p ai-assistant/conversationscd ai-assistant

第二步:配置管理

# config.pyimport osfrom dataclasses import dataclass@dataclassclass Config:    """配置管理"""    api_key: str = os.getenv("DEEPSEEK_API_KEY""")    base_url: str = "https://api.deepseek.com"    model: str = "deepseek-chat"    max_history: int = 10  # 最多保留10轮对话    timeout: int = 30      # 超时时间(秒)    def validate(self):        """验证配置"""        if not self.api_key:            raise ValueError("请设置 DEEPSEEK_API_KEY 环境变量")        return True# 全局配置实例config = Config()
说明
  1. 使用 dataclass 简化代码
  2. API Key 从环境变量读取
  3. validate() 方法检查配置是否正确

第三步:角色管理

# roles.pyfrom typing import Dictclass RoleManager:    """角色管理器"""    # 预定义角色    ROLES: Dict[strstr] = {        "default""你是一个有帮助的AI助手。",        "translator""""你是一个专业的翻译助手。请将用户输入的内容翻译成目标语言。保持原文的语气和风格。专业术语保持原样或给出注释。""",        "code_reviewer""""你是一个资深的Java代码审查专家。请从以下维度审查代码:1. 安全问题(SQL注入、XSS、敏感信息泄露)2. 空指针风险3. 资源管理(连接、流是否关闭)4. 性能问题5. 代码规范(命名、注释、格式)输出格式使用Markdown。""",        "doc_helper""""你是一个技术文档助手。帮助用户:- 整理需求文档- 生成API文档- 润色技术文章保持专业、清晰的语言风格。""",        "sql_expert""""你是一个SQL专家。帮助用户:- 优化SQL查询- 解释执行计划- 设计数据库表结构给出具体的SQL语句和解释。"""    }    @classmethod    def get_role(cls, role_name: str) -> str:        """获取角色Prompt"""        return cls.ROLES.get(role_name, cls.ROLES["default"])    @classmethod    def list_roles(cls) -> list:        """列出所有角色"""        return list(cls.ROLES.keys())
使用方式
# 获取角色role_prompt = RoleManager.get_role("code_reviewer")# 列出所有角色roles = RoleManager.list_roles()# ['default''translator''code_reviewer''doc_helper''sql_expert']

第四步:历史管理

# history.pyimport jsonimport osfrom datetime import datetimefrom typing import ListDictclass HistoryManager:    """对话历史管理器"""    def __init__(self, storage_dir: str = "./conversations"):        self.storage_dir = storage_dir        os.makedirs(storage_dir, exist_ok=True)    def save(self, conversation_id: str, messages: List[Dict]):        """保存对话历史"""        filepath = os.path.join(self.storage_dir, f"{conversation_id}.json")        data = {            "id": conversation_id,            "created_at": datetime.now().isoformat(),            "messages": messages        }        with open(filepath, 'w', encoding='utf-8'as f:            json.dump(data, f, ensure_ascii=False, indent=2)    def load(self, conversation_id: str) -> List[Dict]:        """加载对话历史"""        filepath = os.path.join(self.storage_dir, f"{conversation_id}.json")        if not os.path.exists(filepath):            return []        with open(filepath, 'r', encoding='utf-8'as f:            data = json.load(f)            return data.get("messages", [])    def list_conversations(self) -> List[Dict]:        """列出所有对话"""        conversations = []        for filename in os.listdir(self.storage_dir):            if filename.endswith('.json'):                filepath = os.path.join(self.storage_dir, filename)                with open(filepath, 'r', encoding='utf-8'as f:                    data = json.load(f)                    conversations.append({                        "id": data["id"],                        "created_at": data["created_at"],                        "message_count"len(data["messages"])                    })        # 按时间倒序排列        conversations.sort(key=lambda x: x["created_at"], reverse=True)        return conversations    def delete(self, conversation_id: str):        """删除对话历史"""        filepath = os.path.join(self.storage_dir, f"{conversation_id}.json")        if os.path.exists(filepath):            os.remove(filepath)
说明
  • 每个对话保存为一个JSON文件
  • 支持保存、加载、列出、删除操作
  • 文件名就是 conversation_id

第五步:API调用器

# api_client.pyfrom openai import OpenAI, APIError, APIConnectionError, RateLimitErrorfrom typing import ListDictOptionalimport loggingfrom config import configfrom roles import RoleManagerlogger = logging.getLogger(__name__)class APIClient:    """API调用器"""    def __init__(self):        config.validate()        self.client = OpenAI(            api_key=config.api_key,            base_url=config.base_url        )    def chat(        self,         messages: List[Dict],         role: str = "default"    ) -> Optional[str]:        """调用API"""        # 添加角色设定        system_prompt = RoleManager.get_role(role)        full_messages = [{"role""system""content": system_prompt}] + messages        try:            logger.info(f"调用API, 角色: {role}, 消息数: {len(full_messages)}")            response = self.client.chat.completions.create(                model=config.model,                messages=full_messages,                timeout=config.timeout            )            result = response.choices[0].message.content            logger.info(f"API调用成功, 返回长度: {len(result)}")            return result        except APIConnectionError:            logger.error("网络连接失败")            return "❌ 网络连接失败,请检查网络"        except RateLimitError:            logger.error("请求太频繁")            return "❌ 请求太频繁,请稍后再试"        except APIError as e:            logger.error(f"API错误: {e}")            return f"❌ API错误: {e}"

第六步:主程序

# main.pyimport uuidfrom typing import ListDictfrom config import configfrom roles import RoleManagerfrom history import HistoryManagerfrom api_client import APIClientclass AIAssistant:    """AI助手主类"""    def __init__(self):        self.api_client = APIClient()        self.history_manager = HistoryManager()        self.current_conversation_id: str = str(uuid.uuid4())        self.current_role: str = "default"        self.messages: List[Dict] = []    def chat(self, user_input: str) -> str:        """对话"""        # 添加用户消息        self.messages.append({"role""user""content": user_input})        # 调用API        response = self.api_client.chat(self.messages, self.current_role)        if response:            # 添加AI回复            self.messages.append({"role""assistant""content": response})            # 保存历史            self.history_manager.save(self.current_conversation_id, self.messages)        return response or "❌ 调用失败"    def switch_role(self, role: str):        """切换角色"""        if role in RoleManager.list_roles():            self.current_role = role            print(f"✅ 已切换到角色: {role}")        else:            print(f"❌ 角色不存在,可选角色: {RoleManager.list_roles()}")    def new_conversation(self):        """新建对话"""        self.current_conversation_id = str(uuid.uuid4())        self.messages = []        print("✅ 已创建新对话")    def load_conversation(self, conversation_id: str):        """加载历史对话"""        self.messages = self.history_manager.load(conversation_id)        self.current_conversation_id = conversation_id        print(f"✅ 已加载对话,共 {len(self.messages)} 条消息")    def list_conversations(self):        """列出所有对话"""        conversations = self.history_manager.list_conversations()        if not conversations:            print("暂无历史对话")            return        for conv in conversations:            print(f"- {conv['id'][:8]}... | {conv['created_at'][:10]} | {conv['message_count']}条消息")# 命令行交互def main():    assistant = AIAssistant()    print("=" * 50)    print("AI助手 v1.0")    print("命令: /role <角色名> /new /load <id> /list /quit")    print(f"可用角色: {', '.join(RoleManager.list_roles())}")    print("=" * 50)    while True:        try:            user_input = input("\n你: ").strip()            if not user_input:                continue            # 命令处理            if user_input.startswith("/"):                parts = user_input.split()                cmd = parts[0]                if cmd == "/quit":                    print("再见!")                    break                elif cmd == "/new":                    assistant.new_conversation()                elif cmd == "/role" and len(parts) > 1:                    assistant.switch_role(parts[1])                elif cmd == "/list":                    assistant.list_conversations()                elif cmd == "/load" and len(parts) > 1:                    assistant.load_conversation(parts[1])                else:                    print("❌ 未知命令")                continue            # 正常对话            response = assistant.chat(user_input)            print(f"\nAI: {response}")        except KeyboardInterrupt:            print("\n再见!")            breakif __name__ == "__main__":    main()

运行效果

$ python main.py==================================================AI助手 v1.0命令: /role <角色名> /new /load <id> /list /quit可用角色: default, translator, code_reviewer, doc_helper, sql_expert==================================================你: 帮我写一个用户登录接口AI: 好的,我来帮你写一个用户登录接口...你: /role code_reviewer✅ 已切换到角色: code_reviewer你: 帮我审查这段代码:def login(username, password): ...AI: 【安全问题】1. 密码明文传输,应使用HTTPS2. 没有防止暴力破解...你: /new✅ 已创建新对话你: /quit再见!

踩过的坑

坑1:对话历史太长
问题:对话越来越多,token消耗大,响应慢。
解决:限制历史长度。
# 在 chat 方法中添加if len(self.messages) > config.max_history * 2:    # 只保留最近的对话    self.messages = self.messages[-config.max_history * 2:]
坑2:角色切换后历史混乱
问题:切换角色后,之前的对话还在,但角色设定变了,上下文不连贯。
解决:切换角色时提示用户是否新建对话。
def switch_role(self, role: str):    if role in RoleManager.list_roles():        self.current_role = role        if self.messages:            print("⚠️ 当前有对话历史,建议使用 /new 创建新对话")
坑3:API超时无响应
问题:有时候API响应很慢,程序卡住。
解决:设置超时时间,给用户反馈。
print("正在思考中...")response = self.api_client.chat(self.messages, self.current_role)

后续优化方向

这个AI助手还可以继续完善:
功能
优先级
说明
流式输出
一边生成一边显示,不用等
Web界面
用Streamlit或Gradio做Web界面
自定义角色
允许用户自定义角色Prompt
导出对话
导出为Markdown或PDF
多模型切换
支持切换不同的模型

写在最后

从第一篇的”AI觉醒”,到这篇的”AI助手”,我已经从一个纯粹的AI用户,变成了一个能自己搭建AI工具的开发者。
这种感觉很棒。
代码不复杂,但很有用。
这就是API调用的魅力:把AI的能力封装成你想要的任何工具。
下一篇,我会分享RAG技术,让AI”读懂”你的私有文档。
如果你也在学AI,欢迎关注我,一起交流。

相关文章
上一篇:《我的第一次API调用:从零开始

作者:Tony | 老程序员,正在学AI

本文是AI学习日记系列的第8篇

关注我,一起学习AI