尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

大模型智能体应用工程拆解:从架构到部署再到技能扩展

大模型智能体应用工程拆解:从架构到部署再到技能扩展 经常有读者私信问我看到网上一堆“AI 助手项目”个个都说得天花乱坠但自己拉下来代码之后根本不知道怎么上手很多项目连跑通都要折腾半天。这个“airi酱”项目从名字就能看出它是偏拟人化、带有“陪伴感”的 AI 助手设计但它的技术内核到底是纯聊天壳子还是真的做了一些工程上有意思的东西这其实才是更值得关心的点。先说我的判断airi酱这类项目的核心价值不在“又接了一个大模型 API”而在于它把“对话 记忆 工具调用 前端交互”这几层拼成了一个可以本地跑起来的完整闭环。如果你只是会调用 OpenAI 或国内大模型 SDK那你缺的往往不是模型能力而是怎么把模型能力封装成一个真正可交互、可扩展、能落地的应用。这篇文章就从工程视角把这个过程拆开讲清楚它解决了什么问题、你要怎么部署、怎么给它加技能、怎么排查常见的坑。文章不会只贴代码也不会假装它是什么“颠覆性架构”。我会尽量用真实项目的落地思路帮你把它的目录结构、配置方式、技能扩展、记忆管理和安全边界都过一遍。读完你应该能独立完成部署并且知道怎么在这个框架上做二次开发。1. airi酱到底是什么类型的技术项目很多人第一次看到“airi酱”会下意识觉得它是一个“聊天机器人”。这个理解没有错但只对了一半。换个角度看它更像是互联网上常见的 LLM Agent 应用的一种具象化实现用户通过自然语言和助手对话助手在背后负责理解意图、规划任务、调用可用的工具或服务然后把结果组织成自然语言返回。如果只是把用户的问题转给大模型再转回来那是“API 转发层”不是 Agent。两者之间的区别在于Agent 不仅仅是理解语言它还要能代表用户去执行动作。比如用户说“帮我查一下今天的天气”转发层只会返回一段模型生成的文本而一个带工具能力的 Agent 会触发一个天气查询函数拿到实时数据后再回复用户“今天上海 24 度多云夜间有雨”。后者的价值在于可验证、可落地、能和真实系统打通。从材料来看“airi酱”以“酱”字命名明显带拟人化和社区文化的味道这在开源项目里很常见目的是降低技术门槛、增加亲和力。但不能因为名字可爱就轻视它背后的技术设计。通常这类项目会包含几个核心模块模块作用常见实现对话入口接收用户输入并展示回复命令行、Web 界面、QQ/Telegram 等平台接入意图理解判断用户想干什么依赖 LLM 的 function calling 或者提示词路由技能插件执行具体任务Python 函数、外部 API 调用、命令行工具记忆存储保留上下文和长期信息内存 dict、SQLite、向量数据库大模型接入层统一不同模型供应商OpenAI SDK、国内模型 SDK、本地模型 HTTP 服务如果你的目标只是“能聊天”那没必要做这么多层。但如果你想让 AI 助手在真实项目里干活比如查天气、管理待办、执行脚本、检索文档就必须有“工具调用”和“技能扩展”的设计。从工程角度看airi酱的意义是给你演示了一种低门槛的 Agent 工程范式用一个 Python 主程序把所有模块串起来而不是让你从头开始做 Agent 框架。顺着这个思路下面几节会按“核心架构 - 环境准备 - 配置 - 启动 - 扩展技能 - 记忆 - 安全 - 排错”的顺序讲清楚整个落地路径。2. 核心架构与运行原理2.1 对话主循环不管是 airi酱还是其他 LLM 助手所有 Agent 应用最底层都有一个“主循环”。它做的事情可以用伪代码描述while True: user_input input(你) response model.chat(user_input) print(助手, response)看似简单但真实项目不会这么写。真实项目必须处理几个问题用户这句话需不需要调用工具模型应该以什么格式返回工具调用结果多轮对话的历史怎么保存模型返回的内容过长怎么截断这几个问题决定了整个项目的架构。airi酱这类项目通常的做法是引入 LLM 的 function calling 机制。大模型在生成回复时不再只是输出文本而是可以输出一个结构化请求表示“我想调用某个函数”。主程序解析出这个函数名和参数执行对应的 Python 函数再把结果回传给大模型。大模型拿到函数的真实返回值后才生成最终给用户的自然语言回复。2.2 工具注册表为了让模型知道有哪些工具可用项目里一般会维护一个“工具注册表”。每个技能对应一段 JSON Schema 描述包括函数名、参数列表、参数类型、必填项和说明。模型在读入这段描述后才能判断用户当前需求对应哪个函数。一个典型的技能描述长这样{ name: get_weather, description: 根据城市名查询实时天气, parameters: { type: object, properties: { city: { type: string, description: 城市名例如 上海、北京 } }, required: [city] } }在主程序里这个描述会被拼进系统提示词让模型知道当前环境有哪些工具。模型收到用户问题后如果判断需要查询天气就会在回复中带上类似{function: get_weather, args: {city: 上海}}的结构化内容。主程序提取并执行get_weather(上海)拿到结果后再发一轮请求给模型。这个机制是 Agent 项目最核心的部分。理解了它再看 airi酱 的代码就会清晰很多它的技能目录本质上就是一组 Python 函数 一份 JSON Schema 描述。2.3 记忆分层的设计思路聊记忆之前先看一个场景你和助手说“我叫小明帮我记住”然后隔半小时问“我叫什么”。如果助手每次都把完整历史发给模型模型确实能答上来但对话越长token 消耗越大成本越高响应也越慢。所以项目一般会把记忆拆成两层短期记忆保存在当前会话里用于维持本轮对话的上下文连贯性。长期记忆持久化保存用户偏好、关键事实、历史摘要下次对话时还能调用。airi酱这类项目常见实现是先用一个简单的内存对象保存短期对话再通过 JSON 文件或 SQLite 保存长期记忆。这个设计成本低适合个人使用和二次开发。如果你希望长期记忆支持语义检索可以再接一个向量数据库但这会让项目复杂度上升一个台阶建议先用文件存储跑通流程。这里真正容易踩坑的地方是短期记忆不能无限累加。每轮对话都会把历史记录送回模型一旦超出模型上下文窗口请求就会报错。所以工程上必须做截断或摘要。最简单的策略是只保留最近 N 轮对话复杂一点是用模型对之前的对话做摘要然后把摘要作为新的上下文。3. 环境准备与前置条件在动手之前先把环境说清楚。虽然不同版本的 airi酱 可能有差异但大多数 Python 编写的 AI 助手项目对环境要求是相近的。下面以“通用准备步骤”来写具体版本请以项目 README 为准。3.1 基础环境要求依赖项建议要求说明操作系统Windows 10/11、macOS、Linux 均可推荐 Linux 或 macOS命令行体验更好Python3.10 或更高项目核心逻辑基本是 Python包管理工具pip 或 poetry建议先用 pip 配合虚拟环境大模型 API Key至少一个支持 OpenAI 协议的大模型服务均可Git用于拉取仓库版本管理工具如果你的电脑没有安装 Python建议先去官网下载最新稳定版。安装时注意勾选“Add Python to PATH”否则命令行会找不到python命令。3.2 安装步骤通用安装流程如下# 1. 拉取项目代码 git clone 项目仓库地址 cd airi # 2. 创建虚拟环境 python -m venv .venv # 3. 激活虚拟环境 # Windows: .venv\Scripts\activate # macOS / Linux: source .venv/bin/activate # 4. 安装依赖 pip install -r requirements.txt # 5. 如果项目提供 dev 依赖可一并安装 pip install -r requirements-dev.txt几个容易出问题的地方Python 版本过低会导致某些依赖安装失败。如果报错信息里出现SyntaxError或者某个包要求requires-python 3.10先去确认版本。虚拟环境没激活就执行pip install会把依赖装到全局环境容易污染系统 Python。建议每次操作前先确认命令行提示符前面出现了(.venv)。网络问题导致依赖下载慢或失败可以切换 PyPI 国内镜像源把 pip 的 index-url 换成清华或阿里云镜像。3.3 验证环境安装完依赖后先跑一个最简单的命令确认项目能正常加载python -c from airi import __version__; print(__version__)如果输出版本号说明项目至少能被导入。如果这一步就报错优先看是不是依赖没装全或 Python 版本不匹配。后面所有排错都应该从这一步开始因为“项目根本没进去”和“功能有问题”是两种完全不同的排查路径。4. 模型配置与密钥管理4.1 配置文件结构打开项目根目录通常能看到config或conf文件夹。里面一般会有环境变量样例文件和 YAML 配置。一个典型的配置如下# config/config.yaml model: provider: anthropic name: claude-sonnet-4-20250514 temperature: 0.7 max_tokens: 2048 llm: base_url: api_key_env: LLM_API_KEY platform: enable_webui: true enable_cli: true memory: type: sqlite sqlite_path: ./data/memory.db skills: dir: ./skills auto_load: true这里每个配置项的含义配置项作用注意事项provider指定模型供应商不同的 provider 走不同的 SDK 请求方式base_url自定义 API 地址如果使用国内兼容 OpenAI 协议的服务可以填服务商地址api_key_env读取 API Key 的环境变量名不直接把 Key 写死在配置文件里platform.enable_webui是否启动 Web 界面个人使用建议开启便于观察运行状态memory.type记忆存储方式可选 sqlite / json / memoryskills.dir技能插件目录新增技能时会扫描这个文件夹4.2 API Key 放置方式绝对不要把 API Key 硬编码到配置文件或提交到 Git 仓库。正确做法是放在环境变量里让程序启动时去读取# Windows PowerShell $env:LLM_API_KEY sk-你的密钥 # macOS / Linux export LLM_API_KEYsk-你的密钥也可以在当前 shell 的配置文件中永久写入但要注意不要把这个文件泄露出去。如果项目根目录有.env.example那说明项目可能支持通过python-dotenv读取.env文件。这种情况下你可以复制一份.env.example为.env然后填入自己的密钥。.env文件一定要加入.gitignore避免误提交。这里真正容易踩坑的地方是目前国内能直接稳定访问的模型服务有很多如果你使用的服务兼容 OpenAI 协议但项目默认配置写的是anthropic那么即便填了 Key 和 base_url请求也会失败。配置 provider 时一定要看项目支持的供应商列表而不是只看 model name。5. 启动项目与首次对话验证5.1 CLI 模式启动环境变量配置好之后先用命令行模式启动便于观察日志python run.py --config config/config.yaml如果项目支持直接通过模块启动也可以试试python -m airi启动成功后终端一般会显示类似提示[INFO] airi酱 已启动输入 exit 退出对话。 [INFO] 已加载 3 个技能get_weather, get_time, search_web 你这里值得提醒的是第一次启动时不要急着问复杂问题。先问一句“你好”或者“你能做什么”确认模型能正常回复、日志没有报错。之后再问一个需要技能的问题比如“现在几点了”验证工具调用链路。5.2 验证工具调用如果项目自带一个get_time或get_weather技能你可以直接测试。预期流程是你输入“现在几点”。程序判断这个请求需要调用get_time技能。技能执行拿到当前系统时间。程序把时间结果回传给模型。模型生成最终回复例如“现在是北京时间 2025 年 6 月 3 日 14 点 30 分”。日志中如果能看到类似Called skill: get_time的记录就说明链路已经跑通了。如果只看到模型直接返回了文字而没有工具调用日志需要检查技能描述是否被正确加载或者模型是否选择了直接回答而不是调用工具。5.3 预期结果与判断标准我把成功和失败的表现列成一张表方便对照现象判断启动后无报错能输入文字模型有回复基础链路通输入时间/天气问题后日志出现 skill 调用记录工具链路通输入问题后长时间无响应网络问题或 API Key 无效报错显示context_length_exceeded对话历史过长需要清理或缩短上下文报错显示未知技能技能没有正确注册或目录扫描失败如果你用的是 Claude 或国内兼容 OpenAI 协议的服务注意看请求是否引入了tools参数。很多 Agent 项目会把工具描述通过tools字段传给模型而不是塞进 system prompt。不同模型的 function calling 格式有差异项目一般会在 provider 适配层处理这个差异你要做的就是把provider配对。6. 开发一个自定义技能6.1 技能文件应该放在哪里在 airi酱 这类项目里技能通常以 Python 文件的形式放在skills目录下一个文件对应一个技能。项目启动时会扫描该目录导入所有模块并读取每个技能类的元信息。6.2 最小技能实现下面用“获取当前时间”做一个最小示例。假设项目约定每个技能文件必须定义一个继承自BaseSkill的类并提供name、description和run方法。# skills/get_time.py from datetime import datetime from airi.skill import BaseSkill class GetTimeSkill(BaseSkill): name get_time description 获取当前的日期和时间用户询问几点、几号时使用 parameters { type: object, properties: {}, required: [] } def run(self, **kwargs) - str: now datetime.now() return now.strftime(%Y-%m-%d %H:%M:%S)这段代码逻辑很简单模型看到描述后如果判断用户是想知道当前时间就会调用get_timerun方法返回格式化后的时间字符串。主程序把这个字符串回传给模型模型再组织成自然语言。6.3 带参数技能实现再看一个带参数的技能比如“获取城市天气”。这里我们做一个模拟实现不接真实天气 API但结构完全一致# skills/get_weather.py import random from airi.skill import BaseSkill class GetWeatherSkill(BaseSkill): name get_weather description 根据城市名查询天气情况用户询问天气、气温、降雨时使用 parameters { type: object, properties: { city: { type: string, description: 城市名例如 北京、上海、广州 } }, required: [city] } def run(self, city: str) - str: # 模拟天气查询实际项目中替换为真实 API 调用 weather_data { 北京: 晴24 度, 上海: 多云26 度, 广州: 阵雨28 度, } return weather_data.get(city, f{city} 暂无天气数据)写完后重启项目观察日志里是否出现已加载 2 个技能然后输入“北京天气怎么样”。如果模型返回“北京晴24 度”说明自定义技能已经生效。如果模型仍然直接说“抱歉我无法查询实时天气”大概率是技能没有被加载或者模型供应商的 function calling 没有读入parameters描述。额外说一句技能写多了之后建议每个技能只做一件小而清晰的事。技能描述越精确模型就越容易在正确的时机选中它。描述里最忌讳写“万能助手”这类空话模型不是靠语义猜而是靠描述里的关键词和功能边界来判断。7. 记忆管理的工程实现7.1 无记忆状态的问题如果你跳过记忆设计直接启动项目很快就会遇到一个尴尬场景你告诉助手“我叫张三”然后问“我叫什么”它可能答不上来。原因是每次请求都是独立的模型不保存任何会话历史。要让助手记住上下文必须由主程序负责保存历史并在每次请求时把历史一起发给模型。7.2 基于 SQLite 的长期记忆示例下面用一个极简示例演示记忆存储怎么实现。这里以 SQLite 为例因为它是 Python 标准库自带的能力不需要额外安装数据库服务。import sqlite3 import json class MemoryStore: def __init__(self, db_path./data/memory.db): self.conn sqlite3.connect(db_path) self.conn.execute( CREATE TABLE IF NOT EXISTS memory ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT UNIQUE, value TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ) def save(self, key: str, value: str): self.conn.execute( INSERT OR REPLACE INTO memory (key, value) VALUES (?, ?), (key, value), ) self.conn.commit() def load(self, key: str) - str | None: cursor self.conn.execute( SELECT value FROM memory WHERE key ?, (key,) ) row cursor.fetchone() return row[0] if row else None def dump(self) - str: cursor self.conn.execute(SELECT key, value FROM memory) rows cursor.fetchall() return json.dumps(dict(rows), ensure_asciiFalse)使用方式也很简单当模型在回复中表达“记住某个信息”时主程序可以调用memory.save(key, value)把它存下来。在每轮请求构造系统提示词时调用memory.dump()把已保存的记忆拼进去让模型具备“跨会话记忆”的能力。短期记忆的实现则更轻量直接在内存里维护一个列表即可conversation_history: list[dict] [] def add_message(role: str, content: str): conversation_history.append({role: role, content: content}) # 简化策略只保留最近 10 轮防止上下文超长 if len(conversation_history) 20: conversation_history[:2] []这段代码的关键是 “截断”。它永远只保存最近 20 条消息避免历史无限膨胀。你也可以在截断前让模型把被丢掉的历史压缩成摘要再把摘要作为第一条消息注入。这种方式简单有效适合大多数个人项目和中小型应用。8. 安全边界与权限控制很多二次开发者在给助手添加技能时会忽略一个严重问题大模型返回的“工具调用”本质上是模型生成的文本它不是可信代码。如果你不加限制地让模型调用任意系统命令可能造成安全事故。举个例子一个“执行命令”技能看起来很方便但如果提示词注入攻击者伪造了一段内容诱导模型生成rm -rf /或curl 恶意脚本 | bash后果不堪设想。即使项目只供个人使用也要建立基本的安全边界。建议从以下四个维度控制技能白名单只注册项目允许执行的技能。每一个新增技能都必须经过审核不能允许“动态加载任意 Python 文件”。参数校验技能函数内部必须校验参数。例如城市名参数只能匹配预置列表文件路径只能指向允许的目录不能接受../或绝对路径。命令执行黑名单/白名单如果技能必须执行系统命令优先使用subprocess.run配合白名单命令列表不要直接拼接字符串后交给 shell。日志审计对所有工具调用记录日志包括调用时间、用户输入、技能名称、参数和返回结果。这样即使出了事故也可以回溯。下面是一段带安全校验的“读写文件”技能示例# skills/file_reader.py import os from pathlib import Path from airi.skill import BaseSkill # 只允许读取项目 data 目录下的文件 ALLOWED_ROOT Path(./data).resolve() class ReadFileSkill(BaseSkill): name read_file description 读取指定文本文件的内容仅在用户要求查看文件时使用 parameters { type: object, properties: { filename: { type: string, description: 文件名例如 notes.txt } }, required: [filename] } def run(self, filename: str) - str: target (ALLOWED_ROOT / filename).resolve() if not str(target).startswith(str(ALLOWED_ROOT)): return 拒绝访问文件不在允许目录内 if not target.exists(): return 文件不存在 return target.read_text(encodingutf-8, errorsignore)这个示例的核心不是读文件而是展示“路径穿越防御”。实际项目中凡是涉及文件系统、网络请求、命令执行的技能都应该做类似校验。安全不是可选项而是 Agent 项目中必须内建的一层设计。9. 常见问题与排查思路根据大多数类似项目部署时的反馈我把高频问题整理成表格问题现象可能原因排查方式解决方案启动即报 ModuleNotFoundError依赖未安装完整或 Python 版本不对查看报错模块名确认是否在 requirements.txt 中重新安装依赖升级到项目要求的 Python 版本模型无响应 / 请求超时网络不通、API Key 失效、base_url 配置错误先用 curl 测试服务端连通性再检查环境变量更换网络检查 Key 是否有效确认 base_url 是否匹配 provider对话历史超过上下文限制未做历史截断或截断策略太宽松查看报错中的 token 数确认上下文窗口上限减少保留轮数或接入摘要压缩技能未被加载技能目录路径不对、类名不符合约定、装饰器缺失启动时观察技能加载日志检查技能类的继承关系和命名工具调用总是失败模型不支持 function calling或参数 Schema 与模型要求不匹配打印实际发送给模型的请求体切换 provider或调整 parameters 格式中文乱码终端编码问题或 JSON 序列化未指定 ensure_ascii在终端执行chcp 65001Windows在输出 JSON 时指定ensure_asciiFalse项目升级后配置失效配置项名称或格式发生变化对比新版本 README 和默认配置文件重新生成配置文件不要直接沿用旧配置显存不足或本地模型加载慢本地跑大模型硬件资源不足查看进程占用观察加载日志改用 API 模型或换更小的量化模型排查时记住一个原则从分层角度逐层确认不要一上来就改代码。先确认环境能导入项目再确认模型连通再确认技能加载最后才去看业务逻辑。每一步都能用一行命令或一条日志验证避免把问题混在一起。10. 最佳实践与工程建议10.1 配置管理不建议把配置写成“能用就行”的单个文件。更稳妥的做法是区分默认配置、本地配置和生产配置。开发时可以用config.local.yaml覆盖默认值这个文件加入.gitignore。部署到服务器时用环境变量注入敏感信息或者借助外部配置中心统一管理。10.2 日志规范项目跑起来之后日志是排错的第一信息来源。建议至少输出以下信息每次请求的模型名称、token 消耗。每次技能调用的技能名、参数。每次报错的堆栈。启动时加载的技能列表。日志格式推荐时间 级别 模块 消息比如2025-06-03 14:30:01 INFO skill.get_weather 调用成功参数{city: 上海}耗时 120ms有了这些日志你才能判断一次异常回复到底是模型问题、技能问题还是网络问题。10.3 模型选择策略不要执着于“最强模型”。个人项目和内部工具完全可以按任务复杂度选择轻量模型简单问答、意图路由用响应快、成本低的模型。复杂任务、需要多步推理用能力更强的模型。如果服务支持路由规则可以按技能类型配置不同的模型。判断标准是先跑通流程再优化效果。不要在一开始就把全部复杂度引入项目。10.4 二次开发目录建议如果你打算长期维护一个基于 airi酱 的项目建议按照下面结构组织目录project/ ├── config/ │ ├── config.yaml │ └── config.local.yaml ├── skills/ │ ├── get_time.py │ └── get_weather.py ├── data/ │ ├── memory.db │ └── files/ ├── logs/ │ └── app.log ├── run.py └── requirements.txt这个结构把“配置、技能、数据、日志”分开维护成本会低很多。技能之间不要互相依赖尽量保持每个技能是独立的模块。这样即使某个技能出错也只是影响单个功能不会拖垮整个助手。10.5 版本兼容与升级开源项目更新很快接口变化也很频繁。如果你 fork 了 airi酱 或者直接使用它的代码建议在依赖文件里锁定关键版本而不是每次都用最新。升级之前先看 changelog重点确认配置格式、技能接口和模型 provider 是否有 breaking change。如果项目没有 changelog就对比两次代码的BaseSkill定义这是技能扩展最核心的接口它一变所有自定义技能都需要同步调整。回到最初的问题airi酱 到底值不值得花时间折腾我的答案是值得但不是因为它能聊得来而是因为它是一个看得见、摸得着、能改代码的 Agent 工程样本。你会在这类项目里学到对话循环怎么设计、技能怎么注册、记忆怎么存储、权限怎么控制这些经验在你之后做真正的业务系统时完全用得上。下一步建议你自己动手做三件事第一把它跑起来完成第一次真实对话第二新增一个带有参数的技能比如查询天气或计算器第三把短期记忆截断策略改成“摘要 截断”方案。三件事做完你对 Agent 应用的工程理解会上一个台阶。如果遇到问题按本文第 9 节的排查表逐层检查大部分问题都能定位到具体模块。
返回列表