☰
从零手写AI应用:本地模型部署、提示词与工具调用实践
2026/9/29 1:20:43 网站建设 项目流程

1. 重新理解AI工程:不是调API,而是构建系统

1.1 为什么我要从零手写一个AI应用

这两年AI圈有个现象:很多人说自己在做AI开发,实际上做的事情是把别人的API封装一层,再套个好看的界面。不是说这样不行,但如果你只停留在这一步,对AI系统的理解会一直浮在表面。真正遇到性能问题、成本问题、效果调优问题,你连排查的方向都找不到。

我决定从零开始搭建一套AI应用系统,不依赖任何现成的AI开发框架,用最基础的技术栈,自己处理模型加载、提示词构建、上下文管理、工具调用这些核心环节。这一路走下来,踩了不少坑,也把很多以前模糊的概念彻底搞明白了。这篇博客就是完整的过程记录和经验沉淀。

这套系统最终做成什么样子?一个本地的AI助手,能对话、能联网检索、能调用外部工具完成具体任务,所有代码都是我一行行写出来的,核心依赖只有几个基础库。整个过程让我意识到:AI工程的本质不是某个高深算法,而是把模型能力、程序逻辑、数据流这三样东西有机地组装成一套可靠系统。

1.2 这篇文章适合谁读

如果你属于下面任何一类人,这篇文章都会对你有帮助:

  • 只会调用AI接口,想深入理解AI应用内部工作原理的开发者
  • 想本地部署AI模型,但不知道从哪下手的初学者
  • 已经在用现成框架(比如LangChain、Spring AI之类的),但遇到问题无从排查,想搞清楚底层机制的工程师
  • 纯粹对AI技术好奇,想知道一个AI应用到底是怎么从零跑起来的爱好者

你能从这篇文章里得到:完整的AI应用架构设计思路、每一步选型的理由、核心代码的逐段解析、我在实际操作中踩过的坑和总结的经验教训。不是那种看完就会忘的科普,而是能真正指导你动手实践的干货。

2. 从零搭建的AI应用架构设计与环境准备

2.1 设计目标:为什么选择本地部署而不是纯API方案

动手之前,我先明确了这套系统要达到的目标:

  • 完全本地运行,对话数据不出本机,隐私安全可控
  • 支持模型热切换,不止能用本地模型,也能接入云端API
  • 具备工具调用能力,AI不只聊天,还能执行搜索、读文件、算数据这类实际操作
  • 模块化设计,每个环节都可以单独替换和测试

为什么坚持本地部署?最直接的原因是成本。持续调用云端API做开发和测试,一个月下来费用相当可观,而且每轮对话都有网络延迟,调试体验很差。本地跑模型虽然响应速度受限于硬件,但胜在无额外费用、数据可控、可随时断网调试。

我选的基础模型是Qwen2.5-1.5B-Instruct,理由很实际:它体积适中,量化后不到2GB,普通消费级显卡或者纯CPU都能跑得动;指令跟随能力在同尺寸模型里属于第一梯队;中文支持好,毕竟是国内团队做的模型。如果你硬件条件更好,可以换7B甚至14B版本都不影响整体架构,代码几乎不用改。

2.2 开发环境配置:工具链选型与依赖安装

我的开发环境是这样的:

  • 操作系统:Windows 11,日常开发主力机
  • 开发工具:PyCharm + Jupyter Notebook混合使用
  • 语言版本:Python 3.11
  • 推理框架:llama-cpp-python,直接加载GGUF格式的量化模型
  • 核心依赖:transformers、torch、fastapi、uvicorn、httpx

安装依赖时有一个容易踩坑的地方:llama-cpp-python的安装方式取决于你的硬件环境。CPU版本直接pip装就行,但如果要用CUDA加速,必须指定安装参数,否则默认装出来的是纯CPU版本,性能差好几倍。

# CPU版本 pip install llama-cpp-python # CUDA加速版本(需已安装匹配的CUDA工具包) CMAKE_ARGS="-DLLAMA_CUDA=on" pip install llama-cpp-python --force-reinstall --no-cache-dir

我实测下来,同样一个1.5B模型,CPU跑大概每秒生成8-10个token,启用CUDA之后能到每秒35-40个token。这个差距直接决定了用户体验,所以如果你的显卡支持,强烈建议用CUDA版本。

这里补充一句为什么要选llama-cpp-python而不是直接用transformers。transformers虽然生态最完善,但模型加载后常驻显存,对硬件要求高,推理速度也偏慢。llama-cpp做了大量量化优化,能跑CPU,内存占用低得多,对个人开发者来说实用得多。缺点是和transformers的API风格不同,刚上手需要适应一下。

2.3 工程目录结构:从一开始就保持清晰

很多初学者喜欢把所有代码堆在一个文件里,图省事。但我建议一开始就建立清晰的目录结构,后面维护和扩展会轻松得多。我的工程目录是这样的:

ai-engineering-from-scratch/ ├── main.py # 启动入口,FastAPI服务 ├── config.py # 全局配置:模型路径、参数、端口等 ├── requirements.txt # 依赖清单 ├── core/ # 核心逻辑层 │ ├── __init__.py │ ├── llm.py # 模型加载与推理封装 │ ├── memory.py # 会话上下文管理 │ └── agent.py # 智能体核心:决策与工具调用循环 ├── tools/ # 工具注册层 │ ├── __init__.py │ ├── search.py # 联网搜索工具 │ └── calculator.py # 计算工具 ├── api/ # API接口层 │ ├── __init__.py │ └── routes.py # 路由定义 └── models/ # 存放模型文件

这个结构遵循一个核心原则:模块之间单向依赖。core层的llm.py不含任何业务逻辑,只负责模型的加载和推理;agent.py调用llm和tools,但tools不反向依赖core。这样改任何一层都不会牵动全局。

3. 核心链路拆解:从提示词到模型推理

3.1 模型加载封装:为什么单独抽象一层

模型加载是整个系统最基础也最容易出问题的一环。直接在主程序里写加载逻辑虽然简化了代码量,但会让后续的模型切换、参数调整全部变得僵化。我单独抽了一个llm.py,把所有模型相关的操作集中在这里。

# core/llm.py from llama_cpp import Llama from config import MODEL_PATH, MODEL_PARAMS class LocalLLM: def __init__(self): self.model = None self.system_prompt = None self.load_model() def load_model(self): try: self.model = Llama( model_path=MODEL_PATH, n_ctx=4096, # 上下文窗口大小 n_threads=8, # CPU线程数 n_gpu_layers=-1, # -1表示全部层加载到GPU verbose=False # 关闭冗长日志 ) except Exception as e: print(f"模型加载失败,请检查模型文件路径:{e}") def set_system_prompt(self, prompt: str): self.system_prompt = prompt def generate(self, messages: list, **kwargs) -> str: # 将消息列表转换为模型需要的格式 prompt = self._build_prompt(messages) response = self.model( prompt=prompt, temperature=kwargs.get("temperature", 0.7), max_tokens=kwargs.get("max_tokens", 2048), stop=kwargs.get("stop", None) ) return response["choices"][0]["text"].strip()

这里有几个参数值得单独说一下。

n_ctx=4096是上下文窗口大小。窗口越大,模型能记住的对话历史越长,但内存占用和计算量也会相应增加。对于1.5B这个量级的模型,4096是一个比较均衡的选择,既能覆盖大部分日常对话场景,又不会让资源消耗失控。

n_gpu_layers=-1表示把所有层都放到GPU上计算。如果你的显存不够,可以改成具体的层数,比如n_gpu_layers=20,让部分层留在CPU上。这个参数是性能调优的关键,值得多试几次找到自己硬件条件下的最优值。

生成参数方面,temperature控制随机性:越低越稳定、越遵循上下文,适合问答和代码生成;越高越有创造性,适合头脑风暴和文案写作。我默认设0.7,但实际使用中会根据场景动态调整——处理代码任务时降到0.2,写文案时升到0.9。

3.2 提示词模板:AI对话质量的隐形分水岭

很多人以为提示工程就是在系统提示词里写"你是一个有用的AI助手"就完事了。实际上,一套好的提示词模板对输出质量的影响,有时比换一个更大的模型还明显。我花了不少时间打磨系统提示词,最终定型为这样一段结构化的模板:

# config.py SYSTEM_PROMPT_TEMPLATE = """你是构建在本地环境中的AI助手Neo,运行在Qwen2.5-1.5B模型之上。 你具备以下能力:1. 日常对话与知识问答 2. 使用工具进行联网检索和数学计算 3. 帮助你起草、总结和翻译各类文本。 你必须遵守以下规则: - 回答使用中文,语气自然、友好、专业 - 不知道的信息必须明确说"不知道",绝对不能编造 - 涉及计算、检索类任务时,必须调用工具获得结果后再回答,不能凭空猜测 - 回答长度控制得当,日常对话不冗长,专业问题要详细 - 当用户输入不完整或意图不明确时,用一到两个追问澄清意图 当前时间是:{current_time}"""

这个模板里的三个部分各有作用。第一段建立角色的身份和能力边界,让模型知道它到底能干什么;第二段设定行为规则,尤其"不知道就说不知道"和"必须调用工具"这两条,直接决定了模型会不会一本正经地胡说八道;第三段加上当前时间,对涉及时效性信息的问题非常有用——模型本身不知道现在是哪天,你不告诉它,它就只能瞎猜。

一个值得注意的细节:{current_time}这个占位符需要在每次对话前动态填充。我把这个逻辑放在生成消息的函数里,确保模型看到的时间永远是当前真实时间。

3.3 消息结构与上下文管理:让AI记住你说的每一句话

大语言模型本身没有记忆功能,它能在一次请求中处理的内容就是输入给它的所有文本。所以对话记忆的本质是:把历史对话拼接到新的请求里,一起发给模型。这个拼接策略直接决定了对话质量。

# core/memory.py class ConversationMemory: def __init__(self, max_history=20): self.history = [] self.max_history = max_history def add(self, role: str, content: str): self.history.append({"role": role, "content": content}) # 超出最大长度时,丢弃最早的对话 if len(self.history) > self.max_history: self.history = self.history[-self.max_history:] def build_messages(self, current_input: str): # 组装完整消息列表:系统提示词 + 历史对话 + 当前输入 system_prompt = SYSTEM_PROMPT_TEMPLATE.format( current_time=time.strftime("%Y-%m-%d %H:%M:%S") ) messages = [{"role": "system", "content": system_prompt}] messages.extend(self.history) messages.append({"role": "user", "content": current_input}) return messages

max_history=20这个限制是经过实测的。把全部的对话历史一股脑塞给模型,一方面增加处理耗时,另一方面模型容易"淹没"在冗长的历史中,对最新指令的注意力反而下降。20轮左右的缓冲既保证了对话的连贯性,又不会拖慢响应。

这里有个细节常被忽略——拼接历史时不能只看轮数,还要看总token数。假如某几轮对话特别长,20轮可能就已经撑爆上下文窗口了。更稳妥的做法是按token数限制截断,但这样实现复杂度会高一些。我目前的方案兼顾了简单和效果,你可以根据自己的场景调整。

4. 让AI真正"干活":工具调用机制的实现

4.1 工具是什么,为什么AI需要工具

纯文本的大语言模型有一个先天的能力边界:它只会预测下一个token,无法真正执行操作。你说"帮我算一下237乘以153",它能按照训练时见过的数学模式给出一个近似答案,但更大的数字、更复杂的算式,它就力不从心了。同样的道理,你让它查最新的新闻,它只能说抱歉,因为它的知识截止在训练数据的最后一天。

工具调用机制解决的就是这个问题。AI不需要自己"会"做这件事,它只需要明白"在做这件事之前,应该先调用某个工具,然后把工具返回的结果整理成答案"。这就像你雇了一个研究员——读者不一定懂所有领域,但知道遇到什么问題该去找哪个专家问。

工具调用的实现逻辑可以概括为三个步骤:

  1. 系统提示词里明确告诉模型有哪些工具可用,以及各自的用途
  2. 模型在生成回复时,通过特殊的输出格式表达"我需要调用某工具,参数是某某"
  3. 程序解析模型的输出,执行对应的函数,把真实结果返回给模型继续生成

4.2 工具注册与格式约定:用JSON协议打通模型与代码

要让模型和程序之间顺畅协作,最关键的是约定一个双方都能理解的通信协议。我选择JSON格式作为工具调用的载体,原因很简单:JSON结构化程度高,可以借助现成的代码解析,模型在训练阶段也见过大量JSON示例,生成这种格式的成功率很高。

# tools/__init__.py TOOL_DESCRIPTIONS = [ { "name": "calculator", "description": "执行数学计算,支持加、减、乘、除、乘方、开方等运算。当你需要进行任何数值计算时使用。", "parameters": { "expression": "需要计算的数学表达式,例如:237*153 或 sqrt(256)" } }, { "name": "web_search", "description": "联网搜索最新信息。当用户询问实时数据、近期新闻或训练数据截止之后发生的事件时使用。", "parameters": { "query": "搜索关键词,尽量精简" } } ]

在系统提示词里,我会追加一段工具说明:

你可以使用以下工具: 1. calculator:数学计算工具,调用格式为 TOOL_CALL: {"name": "calculator", "args": {"expression": "237*153"}} 2. web_search:联网搜索工具,调用格式为 TOOL_CALL: {"name": "web_search", "args": {"query": "关键词"}} 如果想要调用工具,只输出 TOOL_CALL: 开头的一行JSON,不要输出其他内容。

我直接用自定义的TOOL_CALL:前缀,而不是依赖llama-cpp原生支持的function calling接口。原因在于:1.5B小模型对原生function calling的支持并不稳定,经常出现格式错误;自定义前缀简单直接,模型更容易学会。

4.3 智能体主循环:让AI自己决定用不用工具

工具调用机制的灵魂在agent.py里的这个主循环——模型先判断是否有必要调用工具,如果有,程序执行工具,把结果喂回去让模型再生成,直到模型给出最终答案。

# core/agent.py import json import re class Agent: def __init__(self, llm: LocalLLM, tools: dict): self.llm = llm self.tools = tools self.memory = ConversationMemory() def run(self, user_input: str) -> str: self.memory.add("user", user_input) messages = self.memory.build_messages(user_input) # 最多循环3次:防止模型陷入无限的工具调用循环 for _ in range(3): response = self.llm.generate(messages, temperature=0.3) # 判断是否是工具调用 tool_call_match = re.search(r'TOOL_CALL: (\{.*\})', response) if tool_call_match: try: tool_call = json.loads(tool_call_match.group(1)) tool_name = tool_call["name"] tool_args = tool_call["args"] # 执行工具 tool_result = self.tools[tool_name](**tool_args) # 把工具结果作为一条消息追加到对话 messages.append({"role": "assistant", "content": response}) messages.append({"role": "tool", "content": json.dumps(tool_result, ensure_ascii=False)}) except Exception as e: messages.append({"role": "tool", "content": f"工具执行出错:{str(e)}"}) continue # 没有工具调用,说明模型给出了最终回答 self.memory.add("assistant", response) return response # 循环次数耗尽,返回最后一次生成的文本 self.memory.add("assistant", response) return response

主循环的设计有几个精心考虑的细节。

第一,工具调用时把temperature降到0.3,确保模型在结构化输出场景下尽可能稳定;等最终生成回答时再调回0.7,保证语言的自然度。

第二,循环上限设为3次。一个正常的任务通常只需要一次工具调用,最多两次。如果模型反复要求调用工具说明它陷入了死循环,3次上限可以及时刹车。这里还有个隐藏设计:每次循环messages不断追加内容,模型会看到之前调用的结果,不会丢失上下文。

第三,异常处理。工具执行失败不直接抛错,而是把错误信息当作"工具返回结果"塞回对话,让模型基于错误信息生成合理的应对。这个处理方式很关键——相比直接崩溃,用户得到的是"抱歉,我尝试搜索但网络好像开小差了"这样更友好的反馈。

4.4 工具的具体实现:计算器和联网搜索

calculator模块相对简单,核心逻辑是用Python的ast模块安全地解析数学表达式,同时做一层安全检查,防止模型生成一些危害性的代码。这里我简化处理,直接用了eval加白名单的方式:

# tools/calculator.py import math def calculator(expression: str): # 只允许数字、运算符和数学函数白名单 allowed_names = { k: v for k, v in math.__dict__.items() if not k.startswith("__") } allowed_names.update({"abs": abs, "round": round}) # 基本的安全检测,拒绝任何不是数学表达式的输入 if any(char in expression for char in ['import', '__', 'exec', 'open']): return {"error": "表达式包含不安全的字符"} try: result = eval(expression, {"__builtins__": {}}, allowed_names) return {"result": str(result)} except Exception as e: return {"error": f"计算失败:{str(e)}"}

web_search模块则调用了一个公开的搜索API——因为模型不知道训练数据之后的事情,联网搜索正好补上这个短板。我使用的是免费的DuckDuckGo搜索API,不需要注册Key,对本地开发非常友好:

# tools/search.py import httpx def web_search(query: str, max_results: int = 5): url = "https://api.duckduckgo.com/" params = {"q": query, "format": "json", "no_html": 1} try: response = httpx.get(url, params=params, timeout=10) data = response.json() if "RelatedTopics" in data: results = [] for topic in data["RelatedTopics"][:max_results]: if "Text" in topic: results.append({"title": topic.get("Text", ""), "url": topic.get("FirstURL", "")}) return {"results": results} return {"results": []} except Exception as e: return {"error": f"搜索请求失败:{str(e)}"}

实测下来,搜索工具对回答时效性问题的提升非常明显。比如问"最近的AI圈有什么大事件",模型单独回答只能给出一个概括性的模糊答案;调用搜索工具后,能拿到具体的新闻标题和链接,回答质量完全不一样。

5. 从示波器到仪表盘:API封装与系统集成

5.1 为什么用FastAPI做后端服务的接口

核心功能全部跑通之后,我做了一层API封装,让这个AI系统被任何客户端复用,而不仅限于在Python脚本里调用。选FastAPI有天然优势:自动生成交互式API文档、原生支持异步、类型检查到位、性能也不错。

# api/routes.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from core.agent import Agent from core.llm import LocalLLM from tools.calculator import calculator from tools.search import web_search app = FastAPI(title="Local AI Assistant API", version="1.0.0") # 全局初始化模型和智能体 llm = LocalLLM() agent = Agent(llm, tools={"calculator": calculator, "web_search": web_search}) class ChatRequest(BaseModel): message: str class ChatResponse(BaseModel): reply: str @app.post("/chat", response_model=ChatResponse) async def chat(request: ChatRequest): try: reply = agent.run(request.message) return ChatResponse(reply=reply) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/health") async def health(): return {"status": "ok"}

启动服务的命令也很直接:

uvicorn api.routes:app --host 0.0.0.0 --port 8000

这里有个喜闻乐见的工程细节:我用事件循环的方式来避免每次请求重新定义或加载模型。PyCharm的调试技巧和接口测试这里就派上用场了——先用uvicorn自带的--reload参数让代码改成自动生效,再用Postman发起请求,测试接口时不用反复重启服务,效率提高非常多。

5.2 后端服务启动时的初始化顺序:为什么模型加载要放在全局

这个坑值得单独拿出来讲。我第一次写的时候,把模型加载放在了每个请求的处理逻辑里,结果每次对话都要等十几秒的加载时间。后来我改成在模块导入时一次性初始化:

  • app = FastAPI()时创建全局的llm和agent实例
  • 启动服务时完成一次模型加载,之后所有请求共享这个实例
  • 对话请求只做增量推理,不再重新加载模型

这套做法的好处是响应时间从十几秒降到几百毫秒。坏处是模型常驻内存,大约占了3-4GB的内存或显存。对个人开发者来说,这个代价是完全可以接受的——服务只在本机跑,节省资源不如节省时间。

同样的思路也应用在会话管理上。如果用单实例Agent跑所有请求,用户A的对话历史会泄露给用户B;如果每个请求都新建Agent,又会丢掉记忆。折中的方案是为每个会话维护独立的Agent实例,用会话ID做索引。我在这个版本里做了简化,保持一个Agent服务一个用户,实现多点会话记忆,后续需要支撑多用户时再改造。

5.3 增加流式输出:让对话体验更自然

非流式输出有一个很明显的问题:用户要等模型把整段话生成完才能看到结果,在本地模型上尤其明显,1.5B模型生成300字的回答差不多要30秒,用户会以为系统卡死了。

llama-cpp-python原生支持流式输出,但封装方式比较特殊。我改造了LocalLLM.generate方法,增加一个stream参数:

def generate_stream(self, messages: list, **kwargs): prompt = self._build_prompt(messages) stream = self.model( prompt=prompt, temperature=kwargs.get("temperature", 0.7), max_tokens=kwargs.get("max_tokens", 2048), stream=True ) for chunk in stream: delta = chunk["choices"][0]["text"] if delta: yield delta

在FastAPI侧,SSE(Server-Sent Events)是最省事的方式——前端通过EventSource接口接收增量数据,逐字显示,体验非常接近ChatGPT。这个改造让系统的可用性提升了一个量级。

6. 实测效果与调优避坑记录

6.1 核心场景实测:哪些表现超出预期

系统跑通之后,我设计了三组测试,覆盖日常对话、工具调用和组合任务:

第一组测试是简单的知识问答。问"量子纠缠是什么",模型给出的解释基本准确,条理清晰,完全不像是一个1.5B模型说出来的话。这得益于Qwen系列在中文语料上的深厚积累,也验证了我的系统提示词在约束回答风格上起到了预期效果。

第二组测试是工具调用。问"237乘以153等于多少",模型没有像我想象中那样直接给答案,而是先输出了一段TOOL_CALL: {"name": "calculator", "args": {"expression": "237*153"}}的JSON,程序执行后返回36261,模型再基于这个结果给出回答。整个链路在代码里走通的那一刻,就是典型的"流水线跑通"的满足感——AI从"会聊天"变成了"会做事"。

第三组测试是组合任务。问"帮我搜一下最近AI圈有什么新模型发布,然后总结一下要点"。模型先调用搜索工具拿到几条新闻标题,再基于搜索结果生成了一段总结。只是搜索API返回的数据结构比较粗糙,有时标题和摘要混在一起,模型整理出来的内容结构还可以,但提取精度有待提高。

6.2 踩坑记录:三次典型的调试排错过程

调试过程中我最深刻的三个坑,都是在本地环境里踩出来的。

第一个坑是模型加载报错,错误信息指向文件路径不存在。花了半小时才发现是路径里包含中文导致的问题,Windows的Unicode文件和Linux的文件系统处理逻辑不一样。解决办法是模型文件放在纯英文、无空格的目录下,这个经验在本地开发中几乎所有Windows用户都会遇到。

第二个坑是生成的JSON格式不稳定。大模型在8-10轮对话后,偶尔会把TOOL_CALL:后面的JSON写成多行格式,导致re.search匹配失败。我加了一层解析逻辑:先找TOOL_CALL:前缀,再把后面所有的花括号内容取出来做一次JSON规范化,遇到逗号缺失等小问题就用正则先修补,修补不了就直接返回工具调用失败的提示。

第三个坑更隐蔽——CUDA版本装完以后torch版本冲突。llama-cpp-python编译时带了某个特定版本的CUDA运行时,而transformers依赖的torch是另一个版本,两者在底层库上有冲突,加载模型时直接崩溃。排查了整整一个晚上,最后在虚拟环境里把两个包重装成兼容版本才解决。这个教训是:本地AI开发一定要用虚拟环境,把项目依赖隔离开,能少踩80%的环境坑。

6.3 性能测试相关:资源占用与启动时间长

开发中常被问到的一个问题是本地部署一个AI模型需要多少资源。实测数据如下:

  • 内存占用:模型加载后约1.8GB,会话内存额外占用约200MB,总共2GB左右
  • GPU显存:如果完全加载到GPU,需要约3GB显存,没有GPU时纯CPU模式也能跑
  • 首次加载时间:冷启动载入模型约8秒,后续请求平均响应时间3-8秒(取决于回答长度、是否调用工具)
  • CPU推理速度:约8-10 token/s,能接受但缺少流畅感
  • GPU推理速度:约35-40 token/s,日常对话的体感已经和云端API差距不大

如果有一张6GB显存以上的显卡,建议直接上7B模型,对话质量会有质的飞跃。1.5B模型胜在轻量,适合快速验证架构和做学习用途。

6.4 效果调优路线:从1.5B到更大模型的平滑演进

架构设计得好不好,一个重要的检验标准是:你能不能在不改动整体结构的前提下,替换一个更强的模型继续使用。我的这套代码在这点上做得比较充分——把config.py里的MODEL_PATH改一下,重新加载服务,理论上就能从1.5B切换到7B模型。

选择更大的模型还需要调几个参数:

  • n_gpu_layers要重新调整,显存不够就减到20甚至10层
  • n_ctx可以适当加大到8192,更大的模型能更有效地利用长上下文
  • 系统提示词不用改,但temperature可能要根据模型表现微调
  • 工具调用的JSON格式契约不用变,但大模型可能更愿意遵循原生function calling格式

本地部署的好处在这里展露无遗:换模型就是改配置的事,整个工程架构的稳定性得到了验证。

7. 从"能跑"到"好用"的思考与建议

做到这里,系统已经是一个"能跑"的状态了——AI助手能对话、能计算、能联网搜索,整体架构清晰,模块可替换。但距离"好用"还有相当长的路。根据我的实操经验,有几点很想分享。

先把建议列表摆上来,再逐一说明:

  • 如果你想动手练手,先用1.5B模型跑通全流程,再升级大模型
  • 不要一上来就上大模型或复杂框架,先用最小可运行版本验证机制
  • 工具的JSON协议不要过度设计,先满足核心场景再持续迭代
  • 每天用真实场景测框架,看它在什么事情上容易"翻车",再针对性优化

我应该解释第一点的理由。这个项目最大的阻力不是技术,而是硬件条件的约束。如果一开始就强上7B甚至14B模型,加载要等好几分钟,推理慢如蜗牛,很容易让人直接放弃。1.5B模型虽然回答质量一般,但它的"轻量"特性让你可以专注于架构本身——先把代码链路跑通,把工具调用机制调顺,再切换到更大模型,把精力放在优化回答质量上。先用小模型把系统搭起来并跑通,再换大模型优化质量,是我最推荐的技术路径。

这套系统还能继续扩展的方向很多。接入语音识别做成语音助手,增加读取本地文件的工具,做成多轮任务规划和工作流调度,或者接入摄像头做多模态应用——架构不变,只需新增工具和调整提示词。我这个版本虽然没有实现,但每个方向都有明确的落地点。

个人体会:AI工程和传统软件开发有个本质差异——你面对的不是一个确定执行指令的机器,而是一个需要引导和约束的"聪明但不稳定"的伙伴。传统的异常处理思路在这里往往失效,你需要做的是设计一套机制让它尽量发挥所长、尽量少犯错。学会了这种思维方式,比学会某个具体工具重要得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询