1. 项目概述与核心价值最近在折腾本地大模型应用的时候发现了一个挺有意思的项目叫Awareness-Local。这个项目名直译过来是“本地意识”听起来有点玄乎但它的核心目标非常明确让大型语言模型LLM在完全离线的环境下也能拥有对当前时间、日期、用户身份以及本地文件系统的“感知”能力。简单来说它解决了一个大模型本地化部署中的“失忆”问题。我们平时用的ChatGPT或者云端API服务器是知道当前时间的也能根据你的历史对话进行一定程度的上下文理解。但当你把模型比如Llama、Qwen、ChatGLM下载到自己的电脑上通过Ollama、LM Studio或者text-generation-webui来运行时这个模型就像一个与世隔绝的“天才”它知识渊博却不知道“今夕是何年”也不知道你是谁更无法主动读取你电脑里的文档来回答问题。Awareness-Local项目就是为了填补这个空白。它不是一个独立的AI应用而是一个智能体Agent框架的增强插件或系统提示词工程的最佳实践集合。通过精心设计的系统提示词System Prompt和一套可选的、轻量级的本地工具调用方案它能让你的本地大模型“睁开眼”获得基础的上下文感知能力。这对于构建真正实用、个性化的本地AI助手至关重要比如让它帮你整理今天的日程、总结你刚写的文档、或者基于你本地知识库进行问答。这个项目的价值在于其“轻量”与“即插即用”。它不要求你重新训练模型也不依赖复杂的云端服务而是利用了大模型本身遵循指令和上下文学习的能力通过“告诉”它该知道什么信息来显著提升对话的实用性和准确性。接下来我们就深入拆解它的设计思路、具体实现以及如何将它融入到你现有的本地AI工作流中。2. 核心设计思路与架构拆解Awareness-Local的设计哲学非常清晰在最小化侵入性和复杂度的前提下最大化上下文信息的有效性。它主要从两个层面来实现“感知”2.1 信息注入层动态系统提示词这是项目的核心。传统的本地大模型对话系统提示词往往是静态的例如“你是一个有帮助的AI助手”。Awareness-Local将其动态化。在每次对话或会话开始时它会自动生成一个包含实时信息的增强型系统提示词。这个动态提示词通常包含以下几个关键模块时间与日期通过调用系统API获取当前的精确时间、日期、星期几甚至季节。这解决了模型“不知道时间”的根本问题。用户身份可以注入一个预设的用户名如“开发者Edwin”、“用户小明”让模型的回复更具针对性。更高级的实现可以关联系统登录用户名。会话上下文声明本次对话的上下文范围或目标例如“本次对话将围绕用户本地项目文档展开”。能力与约束声明明确告诉模型它现在具备“感知”能力并规定这些信息的使用方式例如“你知道当前时间但仅在回答相关问题时主动提及避免在每个回复开头都重复时间信息”。这种设计的巧妙之处在于它完全遵循了大模型的运作原理。系统提示词是模型在生成回复前最先“看到”并深度理解的文本。将关键上下文信息放在这里相当于在模型思考的“底层操作系统”里写入了环境变量效果比在用户提问中附带这些信息要稳定和显著得多。2.2 工具扩展层轻量级本地函数调用对于一些更复杂的感知需求比如“读取我桌面上的report.md文件并总结”仅靠提示词是不够的。这就需要模型具备“动手能力”。Awareness-Local的另一个设计重点是集成了一套本地化的工具调用Function Calling框架。这里的“工具”指的是运行在你电脑本地的、有严格权限控制的小程序或函数。例如文件系统工具安全地列出目录、读取指定文件内容需用户显式授权路径。系统信息工具获取更详细的系统状态如CPU/内存使用率、网络状态如果项目涉及。应用程序交互工具进阶发送指令到其他本地应用例如让音乐播放器暂停。这个工具层通常通过一个轻量级的“工具服务器”或“适配层”来实现。当模型根据对话判断需要调用工具时例如用户说“看看我的笔记”它会输出一个结构化的请求。这个请求被本地代理捕获然后执行对应的安全本地函数将结果如文件内容作为新的上下文送回给模型由模型生成最终回复。注意工具调用是双刃剑。它极大地增强了能力但也带来了复杂性和安全风险。Awareness-Local的设计通常会强调“最小权限原则”和“显式授权”例如工具只能访问预先配置的少数几个“沙盒”目录而不是整个硬盘。2.3 架构总览整个项目的架构可以理解为一种“夹心层”模式用户 - [前端/聊天界面] - [Awareness-Local 代理层] - [本地大模型] | [动态提示词引擎] [本地工具集] | [系统时钟/文件API]代理层作为中间件拦截所有发送给模型的请求。它的工作是1) 向原始用户消息前附加动态生成的系统提示词2) 解析模型的输出看是否包含工具调用请求3) 执行工具并组织新一轮的对话上下文。动态提示词引擎一个简单的脚本或模块负责收集时间、用户信息等并按照模板生成最终的提示词。本地工具集一系列安全的Python函数或其他可执行模块用于执行具体的本地操作。这种架构确保了它能够灵活地适配各种已有的本地大模型部署方案Ollama, OpenWebUI, llama.cpp等你只需要稍微修改它们的调用方式指向这个“代理层”即可。3. 关键组件与实现细节解析理解了整体思路我们来看看要把Awareness-Local跑起来具体需要哪些组件以及如何配置它们。这里我们以一个典型的基于Python的实现为例。3.1 动态提示词模板这是项目的灵魂。一个好的模板既要信息全面又要避免冗余防止占用过多宝贵的上下文窗口Context Window。下面是一个高度可配置的模板示例# awareness_prompt_template.py import datetime import getpass class AwarenessPromptGenerator: def __init__(self, user_nameNone, allowed_dirsNone): self.user_name user_name or getpass.getuser() # 默认使用系统用户名 self.allowed_dirs allowed_dirs or [] # 允许访问的目录列表 def generate_system_prompt(self): # 获取当前时间信息 now datetime.datetime.now() current_date now.strftime(%Y年%m月%d日) current_time now.strftime(%H:%M:%S) day_of_week [星期一, 星期二, 星期三, 星期四, 星期五, 星期六, 星期日][now.weekday()] # 构建核心提示词 prompt f你是一个运行在用户本地计算机上的智能助手。你被赋予了以下上下文感知能力 1. **时间感知**你知道当前的日期和时间。 - 当前日期{current_date} ({day_of_week}) - 当前时间{current_time} 2. **用户感知**你知道正在与你对话的用户身份。 - 当前用户{self.user_name} 3. **文件系统感知**在用户明确授权和请求下你可以协助处理本地文件。请注意你只能访问以下明确允许的目录路径 {chr(10).join([ - path for path in self.allowed_dirs]) if self.allowed_dirs else - 暂无配置的授权目录} - 任何文件操作都必须基于用户的直接指令且不能超出上述范围。 **重要指令** - 请自然地运用这些信息来使你的回答更准确、更有帮助。例如当用户询问“今天是什么日子”时直接回答日期和星期。 - 除非用户询问或上下文需要避免在每条回复的开头都重复时间和用户信息。 - 当用户请求涉及文件操作时请明确说明你将进行的操作例如“我将读取你‘文档’文件夹下的‘报告.md’文件”并在操作前等待用户最终确认如果实现确认机制。 - 你的核心目标是提供安全、有用、精准的本地化协助。 现在对话开始。 return prompt关键点解析模块化信息将时间、用户、文件权限分点说明结构清晰便于模型理解。安全强调反复强调文件访问的限制和需要“明确授权”这是本地AI安全性的基石。使用指导告诉模型“如何”使用这些信息自然运用避免冗余这能显著提升回复质量避免模型变成复读机。3.2 本地工具调用实现工具调用需要模型、代理、执行端三方的协议。一种简单实现是使用类似 OpenAI 的 Function Calling 格式。首先定义工具列表并告知模型# local_tools.py import json import os from pathlib import Path from typing import List, Optional def list_directory(path: str) - str: 列出指定目录下的文件和文件夹。 try: base_path Path(path).resolve() # 这里应加入安全检查确保path在allowed_dirs内 items os.listdir(base_path) return f目录 {path} 下的内容\n \n.join(items) except Exception as e: return f列出目录时出错{e} def read_file_content(file_path: str) - str: 读取指定文件的内容。 try: # 安全检查 with open(file_path, r, encodingutf-8) as f: content f.read(5000) # 限制读取长度防止上下文爆炸 return f文件 {file_path} 的内容前5000字符\n\n{content}\n except Exception as e: return f读取文件时出错{e} # 定义工具的描述用于生成给模型看的“工具说明书” TOOLS [ { type: function, function: { name: list_directory, description: 列出指定目录下的文件和子目录列表。用于浏览用户允许访问的文件夹。, parameters: { type: object, properties: { path: {type: string, description: 要列出的目录的绝对路径或相对于授权根目录的路径。} }, required: [path] } } }, { type: function, function: { name: read_file_content, description: 读取指定文本文件的内容。用于查看文档、日志或代码文件。, parameters: { type: object, properties: { file_path: {type: string, description: 要读取的文件的绝对路径或相对于授权根目录的路径。} }, required: [file_path] } } } ]然后在代理层中需要处理模型可能返回的工具调用请求。以下是代理逻辑的核心片段# awareness_agent.py (部分代码) import requests import json class AwarenessAgent: def __init__(self, llm_api_url, prompt_generator): self.llm_api_url llm_api_url # 例如 Ollama 的 API 地址 self.prompt_gen prompt_generator def chat_round(self, user_message, conversation_history[]): # 1. 生成本次对话的动态系统提示词 system_prompt self.prompt_gen.generate_system_prompt() # 2. 构建完整的消息历史将系统提示词放在最前面 messages [{role: system, content: system_prompt}] messages.extend(conversation_history) messages.append({role: user, content: user_message}) # 3. 准备请求体将工具定义也发送给模型 request_body { model: qwen2.5:7b, # 你实际使用的模型 messages: messages, tools: TOOLS, # 传入工具定义 tool_choice: auto, # 让模型自行决定是否调用工具 stream: False } # 4. 发送请求到本地LLM服务 response requests.post(self.llm_api_url, jsonrequest_body) llm_response response.json() # 5. 解析响应检查是否有工具调用 message llm_response.get(message, {}) tool_calls message.get(tool_calls, None) final_reply if tool_calls: # 6. 执行工具调用 tool_results [] for tool_call in tool_calls: func_name tool_call[function][name] args json.loads(tool_call[function][arguments]) if func_name list_directory: result list_directory(args[path]) elif func_name read_file_content: result read_file_content(args[file_path]) else: result f未知工具{func_name} tool_results.append({ tool_call_id: tool_call[id], role: tool, name: func_name, content: result }) # 7. 将工具执行结果作为新消息再次发送给模型让它生成面向用户的最终回复 messages.append(message) # 加入模型刚才的回复包含工具调用请求 messages.extend(tool_results) # 加入工具执行结果 # 发起第二轮请求让模型“消化”工具结果并生成最终回答 second_request_body {**request_body, messages: messages} second_response requests.post(self.llm_api_url, jsonsecond_request_body) final_message second_response.json().get(message, {}) final_reply final_message.get(content, ) else: # 没有工具调用直接返回模型回复 final_reply message.get(content, ) return final_reply实操心得安全检查是生命线在list_directory和read_file_content函数中必须添加路径验证逻辑确保请求的路径在allowed_dirs列表内或其子目录下。绝对不能让模型拥有自由访问整个文件系统的能力。上下文管理工具调用涉及多轮消息交换。要妥善管理messages列表确保系统提示词、历史对话、用户问题、工具调用请求和工具结果都在正确的位置否则模型会感到“困惑”。性能考量文件读取最好设置长度限制如上面的5000字符因为大模型上下文窗口有限过长的文件内容会挤占对话空间也可能增加API调用成本token数。4. 集成与部署实战指南理论讲完了我们来点实际的。如何将Awareness-Local的能力集成到你现有的本地AI环境中这里以最流行的Ollama Open WebUI组合为例。4.1 基础环境准备假设你已经安装了 Ollama 并拉取了模型如ollama pull qwen2.5:7b同时也部署了 Open WebUI。我们的目标是在 Open WebUI 中让模型获得感知能力。方案一修改 Open WebUI 的系统提示词模板简易版这是最快的方法但只能实现“时间/用户感知”无法实现工具调用。登录 Open WebUI 管理界面。进入 “模型” - 选择你使用的模型如qwen2.5:7b。找到 “系统提示词” 或 “Model Settings” 区域。将我们之前AwarenessPromptGenerator生成的动态提示词需要你写个小脚本提前生成好粘贴进去。但问题是这个提示词是静态的时间不会自动更新。变通方案你可以写一个简单的脚本定时比如每小时调用AwarenessPromptGenerator生成新提示词并通过 Open WebUI 的 API 更新模型设置。这比较 hacky但可行。方案二部署 Awareness 代理服务器推荐功能完整这是更优雅和强大的方式。我们创建一个独立的 FastAPI 应用作为代理它位于 Open WebUI 和 Ollama 之间。项目结构awareness_local_proxy/ ├── main.py # FastAPI 主应用 ├── prompt_gen.py # 动态提示词生成器 ├── local_tools.py # 工具函数定义 ├── config.yaml # 配置文件模型地址、授权目录等 └── requirements.txtmain.py核心代码from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse import requests import json from prompt_gen import AwarenessPromptGenerator import local_tools import asyncio app FastAPI(titleAwareness Local Proxy) PROMPT_GEN AwarenessPromptGenerator(user_nameLocalUser, allowed_dirs[/Users/YourName/Documents, /Users/YourName/Desktop]) OLLAMA_URL http://localhost:11434/api/chat # Ollama 默认API地址 app.post(/v1/chat/completions) async def chat_completion(request: dict): # 1. 提取用户消息和历史 messages request.get(messages, []) model request.get(model, qwen2.5:7b) # 提取最后一条用户消息 user_msg None for msg in reversed(messages): if msg[role] user: user_msg msg[content] break if not user_msg: raise HTTPException(status_code400, detailNo user message found) # 2. 生成动态系统提示词 system_prompt PROMPT_GEN.generate_system_prompt() # 3. 重组消息将动态系统提示词插入最前 # 注意移除原有的静态system消息如果有 new_messages [{role: system, content: system_prompt}] for msg in messages: if msg[role] ! system: # 过滤掉旧的静态system消息 new_messages.append(msg) # 4. 构建转发给Ollama的请求 ollama_payload { model: model, messages: new_messages, stream: request.get(stream, False), options: request.get(options, {}), tools: local_tools.TOOLS # 传入工具定义 } # 5. 处理流式和非流式响应 if ollama_payload[stream]: async def generate(): # 这里简化处理实际需处理流式响应中的工具调用逻辑更复杂 async with aiohttp.ClientSession() as session: async with session.post(OLLAMA_URL, jsonollama_payload) as resp: async for chunk in resp.content: yield chunk return StreamingResponse(generate(), media_typeapplication/x-ndjson) else: # 非流式处理简化版完整工具调用逻辑参考第3.2节 response requests.post(OLLAMA_URL, jsonollama_payload) return response.json() if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)配置 Open WebUI启动你的代理服务器python main.py。打开 Open WebUI 设置找到 “连接设置” 或 “API 配置”。将 “Ollama API Base URL” 从http://localhost:11434修改为http://localhost:8000你的代理服务器地址。保存。现在所有通过 Open WebUI 发起的对话都会先经过你的Awareness Local Proxy由它注入动态提示词并处理潜在的工具调用再转发给 Ollama。部署注意事项路径安全在config.yaml或环境变量中配置allowed_dirs务必使用绝对路径并限制在最小必要范围。错误处理代理服务器必须有完善的错误处理当 Ollama 服务宕机或工具调用出错时给前端返回友好的错误信息。性能代理会增加少量延迟。确保你的代理服务器和 Ollama 运行在同一台机器或局域网内以减少网络开销。4.2 与更多客户端集成除了 Open WebUI你的代理服务器遵循了部分 OpenAI API 格式这意味着任何兼容 OpenAI API 的客户端如ChatGPT-Next-Web,LangChain,LlamaIndex理论上都可以通过修改 API Base URL 来连接你的代理从而获得感知能力。例如在ChatGPT-Next-Web的部署中你只需在环境变量OPENAI_API_BASE里填入http://your-server:8000/v1它就会将请求发送到你的代理。5. 高级技巧与优化策略基础功能跑通后我们可以追求更丝滑、更智能的体验。以下是一些进阶思路5.1 上下文记忆的持久化目前的动态提示词只在单次请求中有效。要实现跨会话的记忆比如记住用户偏好需要引入外部记忆机制。向量数据库记忆使用ChromaDB或LanceDB等轻量级向量库。将每轮对话的核心摘要由模型生成向量化后存储。每次生成系统提示词时先查询向量库找到与当前对话最相关的历史记忆片段一并注入提示词。这能让模型拥有“长期记忆”。摘要式记忆一种更简单的方法是在每次对话结束时让模型自己总结本次对话的要点例如“用户询问了关于Python装饰器的问题我给出了解释和示例。”然后将这个摘要以文本形式追加到一个日志文件中。下次对话时将这个文件的内容作为上下文的一部分读入。5.2 工具调用的优化与安全增强工具描述优化local_tools.py中工具函数的description字段至关重要。描述越清晰、具体模型调用工具的准确率越高。可以加入示例如“description”: “列出目录内容。例如当用户说‘看看我的文档文件夹里有什么’时使用此工具。参数path应为类似‘/Users/xxx/Documents’的绝对路径。”用户确认机制对于高风险操作如文件删除、执行命令不要直接执行。可以让模型在请求工具时生成一段需要用户确认的文本例如“我准备删除‘test.txt’文件此操作不可逆。请确认是否继续(是/否)”。代理层捕获到这个特殊回复后先将其返回给用户等待用户明确输入“是”之后再真正执行工具调用。工具结果摘要当工具返回的结果非常长如一个大文件的内容直接塞回上下文可能效率低下。可以让模型先对结果进行摘要例如“该文件内容主要讨论了……核心观点有三1…2…3…”。然后将摘要而非全文作为工具执行结果返回。5.3 提示词工程的微调动态提示词模板不是一成不变的。你需要根据所用模型的特性进行微调。指令跟随能力测试有些较小的模型指令跟随能力较弱。你可能需要更强硬、更重复的指令比如用“你必须”、“你务必”等词语并将最重要的指令放在提示词的开头和结尾。减少幻觉对于文件内容可以在提示词中强调“你关于文件内容的回答必须严格基于工具读取到的实际文本不得捏造或推测文件中不存在的信息。”个性化根据你的主要用途调整提示词权重。如果你主要用于编程可以加强“代码理解与生成”相关指令如果用于写作则可以强调“风格模仿”和“灵感激发”。6. 常见问题与故障排除在实际搭建和使用过程中你肯定会遇到各种问题。这里记录一些典型坑位和解决方案。6.1 模型不调用工具或调用错误症状用户明明说“读一下我的笔记”模型却自顾自地开始编造笔记内容而不触发read_file_content工具。排查步骤检查工具描述工具的描述是否足够清晰模型是否理解在什么场景下该调用这个工具尝试用更直白的语言重写描述。检查消息结构确保发送给模型的messages列表中包含了tools字段并且格式正确。使用print或日志功能输出你最终发给 Ollama 的完整请求体对比官方文档。检查模型能力并非所有模型都很好地支持工具调用。确保你使用的模型版本是较新的并且官方文档声明支持function calling或tool use。Qwen2.5、DeepSeek最新版、Llama 3.1等在这方面表现较好。简化测试先从一个最简单的工具如“获取当前时间”开始测试确保整个调用链路是通的。6.2 动态提示词导致回复质量下降症状注入动态提示词后模型变得啰嗦、总是重复时间信息或者反而更“笨”了。解决方案精简提示词过长的系统提示词会占用本应用于理解用户问题的上下文窗口。尽量压缩信息只保留最必要的。例如时间信息可以精简为一行。调整指令位置将最重要的行为指令如“避免重复时间信息”放在系统提示词的最末尾。研究表明模型对提示词开头和结尾的内容记忆更深刻。使用模型原生能力一些新的模型如Llama 3在创建时就可以设置“系统提示词”。如果 Ollama 或你的 WebUI 支持直接设置可以优先使用这种方式可能比通过消息列表插入更稳定。6.3 代理服务器性能瓶颈症状对话响应明显变慢尤其是进行文件操作时。优化方向缓存对于不常变化的信息如用户名、授权的目录列表可以缓存在内存中不必每次请求都重新生成。异步处理使用async/await如httpx.AsyncClient来处理所有网络 I/O调用 Ollama API、读取大文件避免阻塞。流式响应透传对于不涉及工具调用的纯文本对话代理服务器应该直接将 Ollama 的流式响应透传给前端而不是等待全部完成再返回。这能极大提升首字响应时间。工具超时为文件读取等工具设置超时时间防止因读取一个巨大文件而卡死整个请求。6.4 安全疑虑与权限控制这是最重要的问题。永远不要信任来自模型的任意路径。症状模型请求读取/etc/passwd或C:\Windows\System32\config\SAM。防护措施路径白名单这是底线。在工具函数内部必须将请求的路径解析为绝对路径并与预先配置的白名单列表进行比对。不在列表内的路径直接返回“无权访问”。路径规范化防止目录遍历攻击。使用os.path.normpath和os.path.abspath规范化路径并检查规范化后的路径是否以白名单目录开头。沙盒环境考虑在 Docker 容器或虚拟机中运行整个代理和模型即使出现安全问题也能被隔离。审计日志记录所有工具调用的时间、用户、参数和结果。定期审查日志发现异常行为。最后Awareness-Local这类项目的魅力在于它用相对简单的工程手段释放了本地大模型的巨大潜力。它不是一个开箱即用的完美产品而是一个需要你根据自身需求去调整、打磨的框架。从注入第一行动态时间开始到安全地让模型帮你整理文档每一步的实践都会让你对智能体、提示词工程以及本地AI应用有更深的理解。