ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于DeepSeek Harness构建Obsidian知识库AI Agent实战指南

基于DeepSeek Harness构建Obsidian知识库AI Agent实战指南 1. 背景与核心概念在知识管理和笔记领域Obsidian 以其强大的双向链接和本地优先特性成为了许多开发者和内容创作者的“第二大脑”。然而随着笔记库的日益庞大如何高效地组织、检索和利用这些知识成为了一个痛点。与此同时AI Agent 技术的发展为我们提供了新的思路能否让一个智能体深度理解我们的知识库并主动提供帮助这正是DeepSeek Harness的用武之地。简单来说DeepSeek Harness 是一个用于构建、管理和运行 AI Agent 的开发框架。它允许开发者将大型语言模型如 DeepSeek 自家的模型的能力封装成可执行、可交互的智能体。而我们的目标就是利用这个框架打造一个专属于 Obsidian 的 AI Agent。这个专属 Agent 能做什么想象一下当你正在撰写一篇技术博客时它可以自动从你的笔记库中找出相关的概念解释、代码片段或项目经验当你对某个模糊的记忆有印象时可以用自然语言询问它它会帮你定位到具体的笔记它甚至可以根据你过往的笔记风格辅助你生成新内容的大纲或初稿。这不再是简单的全文检索而是一个真正理解你知识脉络的“数字伙伴”。本文将带你从零开始实战开发一个运行在本地、与你的 Obsidian 笔记库深度集成的 AI Agent。我们将使用 DeepSeek Harness 作为开发框架并重点解决如何让 Agent 安全、高效地访问和操作你的本地文件系统即 Obsidian 仓库。无论你是想探索 AI 与知识管理的结合还是希望为自己的工作流添加一个智能助手这篇教程都将提供完整的路径。2. 环境准备与版本说明在开始编码之前我们需要搭建一个稳定、兼容的开发环境。由于涉及本地文件操作和 AI 模型调用对环境版本有一定要求。核心环境清单操作系统Windows 10/11, macOS 10.15, 或主流 Linux 发行版如 Ubuntu 20.04。本文示例将在 macOS 和 Ubuntu 上进行演示。Python版本 3.8 - 3.11。这是 DeepSeek Harness 的主要开发语言。推荐使用 3.9 或 3.10 以获得最佳兼容性。# 检查Python版本 python3 --version # 或 python --version包管理工具pip通常随 Python 安装。建议升级到最新版。pip install --upgrade pip代码编辑器/IDEVisual Studio Code (VSCode) 或 PyCharm。VSCode 对 Python 和 MarkdownObsidian 笔记格式支持良好。Obsidian确保你已安装 Obsidian 并拥有一个正在使用的笔记库Vault。Agent 将读取该库的路径。DeepSeek API 密钥你需要一个 DeepSeek 平台的 API Key 来调用其模型。请前往 DeepSeek 官方平台注册并获取。版本兼容性说明AI 框架和库的迭代速度很快。本文撰写时以 DeepSeek Harness 的通用接口和openai兼容的 SDK 为例进行讲解。实际开发时请务必查阅 DeepSeek Harness 官方文档确认其最新安装方式和 API 变更。示例项目结构预览在开始前我们先规划一下项目目录这有助于理解后续的代码组织。obsidian_agent_project/ ├── .env # 存储API密钥等敏感配置 ├── requirements.txt # Python项目依赖 ├── main.py # Agent主程序入口 ├── agent_core/ # Agent核心逻辑模块 │ ├── __init__.py │ ├── obsidian_reader.py # 负责读取解析Obsidian笔记 │ └── tools.py # 定义Agent可用的工具函数 ├── knowledge_base/ # 可选处理后的知识索引或缓存 └── logs/ # 运行日志3. 核心原理与架构拆解在动手写代码前理解我们要构建的 Agent 是如何工作的至关重要。这不仅仅是一个调用 API 的脚本而是一个具备感知、决策和执行能力的系统。3.1 DeepSeek Harness 与 AI Agent 的基本模型DeepSeek Harness 可以看作是一个“Agent 操作系统”或“编排框架”。它的核心思想是将大语言模型LLM作为“大脑”将自定义的函数Tools作为“手脚”并通过一个运行循环ReAct, ReasonAct 模式将两者结合起来。大脑LLM接收用户的查询Query分析意图并决定下一步该调用哪个工具Tool或者直接生成回答。手脚Tools一系列 Python 函数每个函数都有明确的功能描述。例如search_notes(keyword)get_note_content(file_path)。LLM 根据描述决定是否以及如何调用它们。运行循环ReasonLLM 思考“用户想找关于‘Python装饰器’的笔记我应该先用search_notes工具。”Act框架执行search_notes(“Python装饰器”)函数。Observe函数返回搜索结果如文件路径列表。这个结果会再次送给 LLM 进行下一轮Reason“我找到了三个文件用户可能需要具体内容我应该调用get_note_content来查看第一个文件。”如此循环直到 LLM 认为它已经收集到足够信息可以生成最终答案给用户。3.2 Obsidian 知识库的访问策略我们的 Agent 需要与 Obsidian 仓库交互。安全性和效率是首要原则。只读操作优先在初期Agent 应仅限于读取、搜索和分析笔记内容。避免直接创建、修改或删除笔记除非你非常清楚其后果并做了充分备份。路径处理Obsidian 仓库本质是一个文件夹。我们需要在配置中安全地指定其绝对路径并在代码中正确处理跨平台路径问题使用pathlib库。内容解析Obsidian 笔记是 Markdown 文件。我们需要解析纯文本内容。元数据Front-matter即---之间的 YAML。内部链接[[链接]]这代表了知识图谱是极具价值的信息。标签#tag。3.3 系统架构图概念层面[用户提问] | v [DeepSeek Harness Agent] | |-- 大脑: DeepSeek LLM (通过API) | |-- 工具集 (Tools) | | | |-- search_notes_by_keyword() | |-- get_note_content() | |-- get_notes_linked_to() | |-- summarize_content() (可选) | |-- ... 其他自定义工具 | v [Obsidian 本地文件系统] | v [Markdown 文件解析与处理] | v [结构化数据/文本摘要] | v [LLM 生成最终答案] | v [返回给用户]这个架构确保了 Agent 的能力边界清晰并且所有对本地系统的操作都是通过我们明确授权的工具函数完成的安全可控。4. 完整实战构建 Obsidian 专属 Agent现在我们开始一步步实现这个 Agent。请跟随步骤操作。4.1 初始化项目与安装依赖首先创建项目目录并初始化虚拟环境推荐用于隔离依赖。# 创建项目文件夹 mkdir obsidian_agent_project cd obsidian_agent_project # 创建虚拟环境 (Python 3.9) python3 -m venv venv # 激活虚拟环境 # macOS/Linux: source venv/bin/activate # Windows: # venv\Scripts\activate # 创建依赖文件 requirements.txt编辑requirements.txt添加以下内容。注意deepseek-harness的安装包名称可能根据官方发布而变化此处使用通用的openai库作为与 DeepSeek API 交互的客户端并安装必要的工具库。openai1.0.0 python-dotenv1.0.0 pathlib22.3.0; python_version 3.4 # 对于旧版Python通常不需要 # 添加文件处理和文本解析库 markdown3.5 pyyaml6.0 # 可选用于更复杂的文本处理或检索 # sentence-transformers2.2.0 # faiss-cpu1.7.0安装依赖pip install -r requirements.txt4.2 配置环境变量与 API 密钥永远不要将 API 密钥硬编码在代码中。我们使用.env文件来管理敏感配置。创建.env文件# .env DEEPSEEK_API_KEYyour_deepseek_api_key_here DEEPSEEK_API_BASEhttps://api.deepseek.com # 以DeepSeek官方文档为准 OBSIDIAN_VAULT_PATH/Users/YourName/Documents/ObsidianVault # 你的Obsidian仓库绝对路径然后创建config.py来安全地读取这些配置# config.py import os from pathlib import Path from dotenv import load_dotenv # 加载 .env 文件中的环境变量 load_dotenv() class Config: DEEPSEEK_API_KEY os.getenv(DEEPSEEK_API_KEY) DEEPSEEK_API_BASE os.getenv(DEEPSEEK_API_BASE, https://api.deepseek.com) # 获取Obsidian仓库路径并转换为Path对象 vault_path_str os.getenv(OBSIDIAN_VAULT_PATH) if not vault_path_str: raise ValueError(OBSIDIAN_VAULT_PATH 未在 .env 文件中设置) OBSIDIAN_VAULT_PATH Path(vault_path_str).expanduser().resolve() # 验证路径是否存在 if not OBSIDIAN_VAULT_PATH.exists(): raise FileNotFoundError(f指定的Obsidian仓库路径不存在: {OBSIDIAN_VAULT_PATH}) # 模型名称根据DeepSeek API文档填写 MODEL_NAME deepseek-chat # 示例请替换为实际可用模型名 config Config()4.3 实现 Obsidian 笔记读取器这是 Agent 的“眼睛”。我们将创建一个类来负责扫描仓库、解析 Markdown。# agent_core/obsidian_reader.py import re from pathlib import Path from typing import Dict, List, Optional, Tuple import frontmatter # 需要安装pip install python-frontmatter import markdown class ObsidianReader: def __init__(self, vault_path: Path): self.vault_path vault_path self._file_cache {} # 简单缓存避免重复读取 def get_all_note_paths(self) - List[Path]: 获取仓库中所有 .md 文件的路径列表 md_files list(self.vault_path.rglob(*.md)) # 可选排除某些目录如模板文件夹 # md_files [f for f in md_files if .templates not in f.parts] return md_files def read_note_content(self, file_path: Path) - Dict: 读取单个笔记文件解析元数据和内容 if file_path in self._file_cache: return self._file_cache[file_path].copy() if not file_path.exists(): return {error: f文件不存在: {file_path}} try: with open(file_path, r, encodingutf-8) as f: content f.read() # 使用 python-frontmatter 解析元数据 parsed frontmatter.loads(content) metadata parsed.metadata body parsed.content # 提取纯文本去除Markdown标记 html markdown.markdown(body) # 简单去除HTML标签获取纯文本生产环境可用更专业的库如markdown-it plain_text re.sub(r[^], , html).strip() # 提取内部链接和标签 internal_links re.findall(r\[\[([^\]])\]\], body) tags re.findall(r\s#([a-zA-Z0-9_-])\b, body) # 简单匹配可能不完善 note_info { file_path: str(file_path.relative_to(self.vault_path)), title: metadata.get(title, file_path.stem), metadata: metadata, raw_content: body, plain_text: plain_text[:1000], # 限制长度避免上下文过长 internal_links: internal_links, tags: tags, } self._file_cache[file_path] note_info return note_info.copy() except Exception as e: return {error: f读取文件失败 {file_path}: {str(e)}} def search_notes_by_keyword(self, keyword: str, limit: int 5) - List[Dict]: 在全库中搜索包含关键词的笔记简单文本匹配 all_notes [] for md_file in self.get_all_note_paths(): note_info self.read_note_content(md_file) if error not in note_info: # 在标题和纯文本中搜索 if (keyword.lower() in note_info[title].lower() or keyword.lower() in note_info[plain_text].lower()): all_notes.append(note_info) # 按相关性简单排序这里标题匹配优先 all_notes.sort(keylambda x: (keyword.lower() not in x[title].lower(), -len(x[plain_text]))) return all_notes[:limit]4.4 定义 Agent 的工具Tools工具是 Agent 能力的载体。我们将上面读取器的功能包装成标准的 Tool 函数。# agent_core/tools.py from typing import Type from pydantic import BaseModel, Field from .obsidian_reader import ObsidianReader from config import config # 初始化阅读器全局单例避免重复初始化 _reader ObsidianReader(config.OBSIDIAN_VAULT_PATH) # --- 工具1搜索笔记 --- class SearchNotesInput(BaseModel): keyword: str Field(description用于搜索笔记的关键词) limit: int Field(default5, description返回结果的最大数量) def search_notes(keyword: str, limit: int 5) - str: 根据关键词在Obsidian知识库中搜索相关笔记。 notes _reader.search_notes_by_keyword(keyword, limit) if not notes: return f未找到包含关键词 {keyword} 的笔记。 result_lines [] for i, note in enumerate(notes, 1): result_lines.append( f{i}. **{note[title]}** (路径: {note[file_path]})\n f 摘要: {note[plain_text][:150]}...\n f 链接: {note[internal_links][:3] if note[internal_links] else []}\n f 标签: {note[tags][:5] if note[tags] else []} ) return \n\n.join(result_lines) # --- 工具2获取笔记详情 --- class GetNoteDetailInput(BaseModel): file_path: str Field(description笔记在仓库中的相对路径例如 Projects/AI Agent 设计.md) def get_note_detail(file_path: str) - str: 获取指定路径笔记的详细内容。 full_path config.OBSIDIAN_VAULT_PATH / file_path if not full_path.suffix: full_path full_path.with_suffix(.md) note_info _reader.read_note_content(full_path) if error in note_info: return f错误: {note_info[error]} response [ f# {note_info[title]}, f**路径**: {note_info[file_path]}, ---, ## 元数据, fyaml\n{note_info[metadata]}\n, ---, ## 内容预览, note_info[plain_text][:500] (... if len(note_info[plain_text]) 500 else ), ---, ## 内部链接, , .join(note_info[internal_links]) if note_info[internal_links] else 无, ## 标签, , .join(f#{tag} for tag in note_info[tags]) if note_info[tags] else 无, ] return \n.join(response) # 将所有工具和其输入模型组织起来供Agent使用 TOOLS [ { type: function, function: { name: search_notes, description: 在Obsidian知识库中搜索包含特定关键词的笔记。, parameters: SearchNotesInput.model_json_schema(), }, }, { type: function, function: { name: get_note_detail, description: 根据文件相对路径获取特定笔记的详细内容、元数据和链接。, parameters: GetNoteDetailInput.model_json_schema(), }, }, ]4.5 构建 Agent 主程序与运行循环这里我们使用 OpenAI 兼容的客户端来调用 DeepSeek API并手动实现一个简单的 ReAct 循环来演示原理。在实际项目中你可以使用 LangChain、Semantic Kernel 或 DeepSeek Harness SDK 来更优雅地实现。# main.py import json import sys from openai import OpenAI from config import config from agent_core.tools import TOOLS, search_notes, get_note_detail # 初始化OpenAI客户端指向DeepSeek API client OpenAI( api_keyconfig.DEEPSEEK_API_KEY, base_urlconfig.DEEPSEEK_API_BASE, ) # 工具名称到实际函数的映射 TOOL_MAP { search_notes: search_notes, get_note_detail: get_note_detail, } def run_agent_conversation(user_query: str, max_turns: int 5): 运行一个简单的ReAct循环来处理用户查询。 messages [ {role: system, content: 你是一个专用于查询用户个人Obsidian知识库的助手。你可以使用工具来搜索笔记或查看笔记详情。请根据用户问题决定是否需要使用工具并严格按工具要求的格式调用。最终答案应基于工具返回的信息清晰、有条理地总结给用户。}, {role: user, content: user_query} ] print(f\n用户: {user_query}) print(- * 40) for turn in range(max_turns): # 1. 调用LLM决定行动 response client.chat.completions.create( modelconfig.MODEL_NAME, messagesmessages, toolsTOOLS, tool_choiceauto, ) response_message response.choices[0].message messages.append(response_message) # 将助手的响应加入历史 # 2. 检查是否需要调用工具 tool_calls response_message.tool_calls if not tool_calls: # 没有工具调用直接返回最终答案 final_answer response_message.content print(f助手: {final_answer}) return final_answer # 3. 执行工具调用 for tool_call in tool_calls: function_name tool_call.function.name function_to_call TOOL_MAP.get(function_name) if not function_to_call: result f错误: 未知工具 {function_name} else: try: function_args json.loads(tool_call.function.arguments) print(f助手调用工具: {function_name}({function_args})) result function_to_call(**function_args) except Exception as e: result f工具执行出错: {str(e)} # 4. 将工具执行结果加入对话历史供LLM下一轮分析 messages.append({ role: tool, tool_call_id: tool_call.id, content: str(result), # 结果必须是字符串 }) print(f工具结果: {result[:200]}...) # 打印部分结果 # 如果循环达到最大轮数仍未结束 return 对话轮次已达上限未能完成查询。 if __name__ __main__: # 示例交互式对话 print(Obsidian 专属 Agent 已启动输入您的问题输入 quit 退出) while True: try: query input(\n您: ) if query.lower() in [quit, exit, q]: print(再见) break if query.strip(): run_agent_conversation(query) except KeyboardInterrupt: print(\n程序被中断。) break except Exception as e: print(f发生错误: {e})4.6 运行与验证确保.env文件已正确配置。在项目根目录下运行主程序python main.py你将看到提示符。尝试提问“我的笔记里有哪些关于 Python 的内容”“帮我找一下关于‘深度学习’的笔记。”“查看 ‘Projects/AI Agent 设计.md’ 这个文件的内容。”观察控制台输出你会看到 Agent 的思考过程调用哪个工具、传入什么参数以及工具返回的结果最终 LLM 会基于这些结果生成一个整合后的答案。5. 常见问题与排查思路在开发和使用过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案启动报错ModuleNotFoundError依赖未安装或虚拟环境未激活。1. 确认已激活虚拟环境 (venv)。2. 运行pip install -r requirements.txt重新安装。API 调用失败AuthenticationErrorAPI 密钥错误、过期或未设置。1. 检查.env文件中的DEEPSEEK_API_KEY是否正确无误。2. 确认 API 密钥是否有调用权限或余额。3. 检查config.py中是否正确加载了.env文件。找不到 Obsidian 笔记仓库路径配置错误或笔记文件不存在。1. 检查.env中的OBSIDIAN_VAULT_PATH必须是绝对路径。2. 在config.py中添加print(config.OBSIDIAN_VAULT_PATH)验证路径。3. 确保路径下有.md文件。Agent 不调用工具直接胡编乱造系统提示词System Prompt不清晰或工具描述不准确。1. 强化main.py中system角色的提示词明确其职责和限制。2. 检查tools.py中每个工具的description和parameters描述是否清晰无歧义。3. 尝试在用户提问中更明确地要求“搜索”或“查看”。工具调用参数错误LLM 未能正确理解用户意图并格式化参数。1. 在tool_calls解析后打印function_args查看实际参数。2. 考虑在工具函数的输入模型如SearchNotesInput中使用更严格的字段描述和示例。处理大量笔记时速度慢每次搜索都全量遍历和读取文件。1. 在ObsidianReader中实现缓存机制示例代码已包含简单缓存。2. 考虑为笔记内容建立本地向量数据库索引如使用sentence-transformers和FAISS实现语义搜索。解析 Markdown 元数据出错笔记的 Front-matter 格式不规范。1. 使用python-frontmatter库通常能处理大部分情况。2. 在read_note_content函数中添加更完善的异常捕获和日志对解析失败的笔记跳过或记录。内存占用过高一次性加载了所有笔记内容到内存。1. 避免在search_notes_by_keyword中一次性处理所有文件可以分批次。2.plain_text字段只存储摘要如前1000字符而非全文。6. 最佳实践与工程建议将一个小型 Demo 转化为一个稳定、可用的工程系统需要考虑更多因素。6.1 安全性是第一要务权限最小化Agent 应运行在单独的、权限受限的用户或容器中。确保其只有对 Obsidian 仓库的读取权限除非你明确需要写入功能。输入验证与清理所有从用户输入或笔记内容中获取并用于工具函数参数如文件路径的数据都必须进行严格的验证和清理防止路径遍历攻击如../../../etc/passwd。API 密钥管理永远不要将.env文件提交到 Git。使用.gitignore将其排除。在生产环境中应使用更安全的密钥管理服务如 HashiCorp Vault、AWS Secrets Manager。6.2 性能与可扩展性建立索引对于超过几百篇的笔记库全量扫描是不可接受的。最佳实践是建立一个离线索引流程。定期如每天运行一个脚本扫描仓库将笔记的标题、路径、纯文本摘要、以及通过嵌入模型Embedding Model生成的向量存储到本地数据库如 SQLite或向量数据库如 Chroma, Qdrant。Agent 的search_notes工具将改为查询这个索引实现毫秒级响应。异步处理如果工具操作涉及网络请求如调用其他 API或大量 IO应使用异步编程asyncio来避免阻塞主循环。流式输出对于需要生成较长回答的场景可以考虑使用 LLM 的流式响应提升用户体验。6.3 提升 Agent 的智能与可靠性更丰富的工具集get_notes_linked_to(note_title): 查找所有链接到某篇笔记的笔记用于探索知识关联。summarize_content(file_path): 调用 LLM 对长笔记进行摘要。answer_based_on_context(question, context_notes): 在提供相关笔记上下文后让 LLM 进行深度问答。更好的提示工程为系统提示词提供更具体的角色设定、约束条件和输出格式要求。使用Few-Shot Prompting在系统消息中提供几个“用户提问-工具调用-最终回答”的示例引导 Agent 更好地使用工具。引入记忆让 Agent 能记住对话历史中的关键信息实现多轮对话的连贯性。这可以通过在messages中保留历史记录来实现注意上下文长度限制或引入更复杂的外部记忆模块。6.4 部署与集成命令行工具 (CLI)将main.py包装成一个命令行工具支持更多参数如指定仓库路径、输出格式等。Web 服务使用 FastAPI 或 Flask 将 Agent 封装成 REST API方便与 Obsidian 插件或其他前端集成。Obsidian 插件终极目标是开发一个 Obsidian 插件在笔记界面内直接与 Agent 对话。这需要 JavaScript/TypeScript 知识并通过插件 API 调用本地或远程的 Agent 后端服务。6.5 日志与监控为关键操作如工具调用、API 请求、错误添加详细的日志记录。监控 API 的 Token 消耗和费用。记录用户的查询和 Agent 的响应用于后续分析和模型调优注意隐私。通过遵循这些最佳实践你的 Obsidian 专属 Agent 将从一个实验性脚本成长为一个真正能为你的知识工作流提供持续价值的强大工具。
返回列表