# 开发者指南 本指南说明如何扩展和自定义日记整理 Agent,包括新的错误处理模式、配置验证功能和故障排除指南。 ## 架构概览 Agent 系统基于以下核心概念: - **Agent**:主控制器,负责管理 Commands 和 Skills - **Command**:用户可执行的命令,编排多个 Skills - **Skill**:原子化的功能单元,执行具体任务 - **SkillChain**:多个 Skills 的有序执行链 - **ErrorHandler**:集中式错误处理和日志记录 - **ConfigurationValidator**:配置验证和环境变量扩展 ## 核心类 ### Agent ```python from journal_organizer.agent_core import Agent # 创建 Agent agent = Agent("MyAgent", config={}) # 注册命令 agent.register_command(my_command) # 执行命令 result = await agent.execute_command("command_name", args={}) ``` ### Skill 所有 Skill 都继承自 `Skill` 基类: ```python from journal_organizer.agent_core import Skill, SkillType, SkillResult, CommandContext class MySkill(Skill): def __init__(self): super().__init__( name="my_skill", skill_type=SkillType.ANALYZE, description="我的自定义 Skill" ) async def execute(self, context: CommandContext, **kwargs) -> SkillResult: # 实现您的逻辑 try: result = do_something(**kwargs) return SkillResult( success=True, data=result, message="执行成功" ) except Exception as e: return SkillResult( success=False, error=str(e), message="执行失败" ) ``` ### Command 所有 Command 都继承自 `Command` 基类: ```python from journal_organizer.agent_core import Command, SkillResult, CommandContext class MyCommand(Command): def __init__(self): super().__init__( name="my_command", description="我的自定义命令", aliases=["mc"] ) # 注册 Skills self.register_skill(MySkill()) async def execute(self, context: CommandContext) -> SkillResult: # 获取参数 param1 = context.args.get('param1') # 执行 Skill skill = self.skills['my_skill'] result = await skill.execute(context, param1=param1) return result ``` ## 添加新的 Skill ### 步骤 1: 创建 Skill 类 在 `skills/` 目录下创建一个新文件,例如 `my_skill.py`: ```python from ..agent_core import Skill, SkillType, SkillResult, CommandContext class MyCustomSkill(Skill): def __init__(self): super().__init__( name="my_custom_skill", skill_type=SkillType.TRANSFORM, description="执行自定义转换" ) async def execute(self, context: CommandContext, **kwargs) -> SkillResult: try: input_data = kwargs.get('input_data') # 您的自定义逻辑 output_data = self._process(input_data) return SkillResult( success=True, data=output_data, message="处理完成" ) except Exception as e: return SkillResult( success=False, error=str(e), message="处理失败" ) def _process(self, data): # 实现处理逻辑 return data ``` ### 步骤 2: 在 Command 中使用 Skill ```python from ..skills.my_skill import MyCustomSkill class MyCommand(Command): def __init__(self): super().__init__(name="my_command") self.register_skill(MyCustomSkill()) async def execute(self, context: CommandContext) -> SkillResult: skill = self.skills['my_custom_skill'] return await skill.execute(context, input_data="test") ``` ## 添加新的 Command ### 步骤 1: 创建 Command 类 在 `commands/` 目录下创建一个新文件,例如 `my_command.py`: ```python from ..agent_core import Command, SkillResult, CommandContext from ..skills.my_skill import MyCustomSkill class MyCommand(Command): def __init__(self): super().__init__( name="my_command", description="我的自定义命令", aliases=["mc", "my-cmd"] ) self.register_skill(MyCustomSkill()) async def execute(self, context: CommandContext) -> SkillResult: # 获取参数 param1 = context.args.get('param1') param2 = context.args.get('param2', 'default') # 执行 Skill skill = self.skills['my_custom_skill'] result = await skill.execute(context, input_data=param1) return result ``` ### 步骤 2: 在 Agent 中注册 Command 编辑 `main.py` 的 `_register_commands` 方法: ```python def _register_commands(self) -> None: """注册所有命令""" self.agent.register_command(OrganizeCommand()) self.agent.register_command(MyCommand()) # 添加新命令 ``` ### 步骤 3: 测试新命令 ```bash python -m journal_organizer my_command --param1 "value1" ``` ## 使用 SkillChain SkillChain 允许您按顺序执行多个 Skills: ```python from journal_organizer.agent_core import SkillChain class MyCommand(Command): def __init__(self): super().__init__(name="my_command") # 创建 Skill 链 chain = SkillChain("my_chain", "执行一系列操作") chain.add_skill(Skill1(), {"param1": "value1"}) chain.add_skill(Skill2(), {"param2": "value2"}) self.register_skill_chain(chain) async def execute(self, context: CommandContext) -> SkillResult: chain = self.skill_chains['my_chain'] return await chain.execute(context) ``` ## 异步编程 所有 Skills 和 Commands 都使用异步编程(async/await)。这允许并发执行多个操作。 ### 基本示例 ```python async def execute(self, context: CommandContext, **kwargs) -> SkillResult: # 异步调用外部 API result = await self.call_external_api() return SkillResult(success=True, data=result) async def call_external_api(self): # 使用 aiohttp 进行异步 HTTP 请求 async with aiohttp.ClientSession() as session: async with session.get('https://api.example.com/data') as resp: return await resp.json() ``` ## 错误处理 ### 新的错误处理框架 系统现在使用集中式错误处理框架,提供一致的错误管理和日志记录: ```python from journal_organizer.error_handling import ( ErrorHandler, JournalOrganizerError, ConfigurationError, APIError, ValidationError ) # 创建错误处理器 import logging logger = logging.getLogger("MySkill") error_handler = ErrorHandler(logger) # 处理 API 错误 try: result = await api_call() except Exception as e: error_result = error_handler.handle_api_error(e, "claude", "analyze_text") return SkillResult( success=False, error=error_result["error"], message=error_result["message"] ) ``` ### 自定义异常类型 使用专门的异常类型来处理不同类型的错误: ```python from journal_organizer.error_handling import ( JournalOrganizerError, ConfigurationError, APIError, ValidationError ) # 配置错误 if not api_key: raise ConfigurationError("API key is required", context={"service": "claude"}) # API 错误 if response.status_code != 200: raise APIError( "API request failed", api_name="obsidian", status_code=response.status_code ) # 验证错误 if not validate_input(data): raise ValidationError("Invalid input format", field="date") ``` ### Skill 中的错误处理模式 在 Skill 中实现标准化的错误处理: ```python from journal_organizer.agent_core import Skill, SkillResult, CommandContext from journal_organizer.error_handling import ErrorHandler, APIError, ValidationError class MySkill(Skill): def __init__(self): super().__init__(name="my_skill") self.error_handler = ErrorHandler(self.logger) async def execute(self, context: CommandContext, **kwargs) -> SkillResult: try: # 输入验证 self._validate_inputs(**kwargs) # 执行主要逻辑 result = await self._perform_operation(**kwargs) return SkillResult(success=True, data=result, message="操作成功") except ValidationError as e: error_result = self.error_handler.handle_validation_error(e, "input_data") return SkillResult( success=False, error=error_result["error"], message=error_result["message"] ) except APIError as e: error_result = self.error_handler.handle_api_error(e, "external_service", "operation") return SkillResult( success=False, error=error_result["error"], message=error_result["message"] ) except Exception as e: # 处理未预期的错误 self.logger.error(f"Unexpected error in {self.name}: {str(e)}", exc_info=True) return SkillResult( success=False, error="Internal error occurred", message="操作失败,请检查日志" ) def _validate_inputs(self, **kwargs): """验证输入参数""" required_params = ['param1', 'param2'] for param in required_params: if param not in kwargs: raise ValidationError(f"Missing required parameter: {param}", field=param) async def _perform_operation(self, **kwargs): """执行主要操作""" # 实现您的逻辑 pass ``` ## 日志记录 使用内置的 logger 记录信息: ```python class MySkill(Skill): async def execute(self, context: CommandContext, **kwargs) -> SkillResult: self.logger.debug("开始执行") self.logger.info("处理数据") self.logger.warning("可能的问题") self.logger.error("发生错误") return SkillResult(success=True) ``` ## 配置管理 ### 新的配置验证系统 系统现在包含强大的配置验证功能,支持类型检查、环境变量扩展和路径验证: ```python from journal_organizer.config_validation import ( SystemConfig, ObsidianConfig, ClaudeConfig, validate_system_config ) # 验证配置 try: config = validate_system_config(raw_config) print("配置验证成功") except ValidationError as e: print(f"配置验证失败: {e}") ``` ### 环境变量扩展 配置文件支持环境变量扩展: ```yaml # config.yaml claude: api_key: "${ANTHROPIC_API_KEY}" # 从环境变量读取 model: "${CLAUDE_MODEL:-claude-3-5-sonnet-20241022}" # 带默认值 obsidian: vault_path: "${OBSIDIAN_VAULT_PATH}" rest_api: api_key: "${OBSIDIAN_API_KEY}" ``` ### 配置验证示例 ```python from journal_organizer.config_validation import validate_obsidian_config, validate_claude_config # 验证 Obsidian 配置 obsidian_config = { "vault_path": "/path/to/vault", "rest_api": { "url": "https://localhost:27123", "api_key": "your-key", "verify_ssl": False } } try: validated_config = validate_obsidian_config(obsidian_config) print("Obsidian 配置有效") except ValidationError as e: print(f"Obsidian 配置错误: {e}") # 验证 Claude 配置 claude_config = { "api_key": "sk-ant-...", "model": "claude-3-5-sonnet-20241022", "max_tokens": 4096 } try: validated_config = validate_claude_config(claude_config) print("Claude 配置有效") except ValidationError as e: print(f"Claude 配置错误: {e}") ``` ### 在 Skill 中访问配置 ```python async def execute(self, context: CommandContext, **kwargs) -> SkillResult: config = context.config or {} # 安全地访问配置 claude_config = config.get('claude', {}) api_key = claude_config.get('api_key') if not api_key: raise ConfigurationError("Claude API key not configured") # 使用配置 return SkillResult(success=True) ``` ## 测试 ### 单元测试示例 ```python import pytest from journal_organizer.agent_core import CommandContext @pytest.mark.asyncio async def test_my_skill(): skill = MySkill() context = CommandContext(command_name="test") result = await skill.execute(context, input_data="test") assert result.success == True assert result.data is not None ``` ### 运行测试 ```bash pytest tests/ ``` ## 性能优化 ### 并发执行 ```python import asyncio async def execute(self, context: CommandContext, **kwargs) -> SkillResult: # 并发执行多个操作 results = await asyncio.gather( self.operation1(), self.operation2(), self.operation3() ) return SkillResult(success=True, data=results) ``` ### 缓存 ```python from functools import lru_cache class MySkill(Skill): @lru_cache(maxsize=128) def expensive_operation(self, key): # 缓存昂贵的操作 return process(key) ``` ## 最佳实践 ### 现代 Python 模式 系统现在遵循现代 Python 最佳实践: #### 1. 使用 f-strings 进行字符串格式化 ```python # ✅ 推荐:使用 f-strings name = "用户" message = f"欢迎 {name},当前时间是 {datetime.now()}" # ❌ 避免:字符串连接 message = "欢迎 " + name + ",当前时间是 " + str(datetime.now()) ``` #### 2. 使用 pathlib 进行文件路径操作 ```python from pathlib import Path # ✅ 推荐:使用 pathlib vault_path = Path(config['obsidian']['vault_path']) daily_folder = vault_path / "Daily" note_file = daily_folder / f"{date}.md" # 检查文件是否存在 if note_file.exists(): content = note_file.read_text(encoding='utf-8') # ❌ 避免:使用 os.path import os note_file = os.path.join(vault_path, "Daily", f"{date}.md") ``` #### 3. 使用 dataclasses 定义数据结构 ```python from dataclasses import dataclass, field from typing import Optional, List from datetime import datetime @dataclass class SkillResult: success: bool data: Optional[Dict[str, Any]] = None error: Optional[str] = None message: str = "" timestamp: str = field(default_factory=lambda: datetime.now().isoformat()) def to_dict(self) -> Dict[str, Any]: return asdict(self) ``` #### 4. 使用类型提示 ```python from typing import Dict, Any, Optional, List, Union async def execute_skill( skill_name: str, context: CommandContext, **kwargs: Any ) -> SkillResult: """ 执行指定的 Skill Args: skill_name: Skill 名称 context: 命令执行上下文 **kwargs: Skill 参数 Returns: SkillResult: 执行结果 """ pass ``` #### 5. 使用异步上下文管理器 ```python from contextlib import asynccontextmanager import aiohttp @asynccontextmanager async def http_client(config: Dict[str, Any]): """HTTP 客户端上下文管理器""" connector = aiohttp.TCPConnector( ssl=False if not config.get('verify_ssl', True) else None ) async with aiohttp.ClientSession(connector=connector) as session: try: yield session finally: await session.close() # 使用示例 async def call_api(): async with http_client(api_config) as client: async with client.get(url) as response: return await response.json() ``` ### 通用最佳实践 1. **单一职责**:每个 Skill 只负责一个任务 2. **错误处理**:使用新的错误处理框架 3. **日志记录**:使用 logger 记录重要信息 4. **配置驱动**:使用配置验证系统 5. **异步编程**:充分利用异步特性提高性能 6. **类型安全**:使用类型提示和验证 7. **文档**:为您的 Skills 和 Commands 编写清晰的文档 8. **测试**:编写单元测试和属性测试确保代码质量 9. **版本控制**:使用 git 管理代码版本 10. **代码格式化**:使用 black 和 isort 保持代码风格一致 ## 示例:完整的自定义 Skill ```python """ 自定义 Skill 示例:文本统计 """ from ..agent_core import Skill, SkillType, SkillResult, CommandContext class TextStatisticsSkill(Skill): """计算文本统计信息的 Skill""" def __init__(self): super().__init__( name="text_statistics", skill_type=SkillType.ANALYZE, description="计算文本的字数、词数、句数等统计信息" ) async def execute(self, context: CommandContext, **kwargs) -> SkillResult: """ 执行文本统计 Args: context: 命令执行上下文 **kwargs: 包含 text 参数 Returns: SkillResult: 包含统计结果的结果 """ try: text = kwargs.get('text', '') if not text: return SkillResult( success=False, error="缺少文本参数", message="未提供要统计的文本" ) # 计算统计信息 stats = { 'char_count': len(text), 'word_count': len(text.split()), 'sentence_count': len(text.split('。')), 'line_count': len(text.split('\n')), 'avg_word_length': len(text) / len(text.split()) if text.split() else 0 } self.logger.info(f"文本统计完成: {stats}") return SkillResult( success=True, data=stats, message="文本统计完成" ) except Exception as e: self.logger.error(f"文本统计失败: {str(e)}") return SkillResult( success=False, error=str(e), message="文本统计异常" ) ``` ## 资源 - [Python 异步编程](https://docs.python.org/3/library/asyncio.html) - [Anthropic API 文档](https://docs.anthropic.com/) - [Obsidian API 文档](https://docs.obsidian.md/Obsidian+API) --- # 故障排除指南 本节提供常见问题的解决方案和调试技巧。 ## 常见问题 ### 1. 导入错误 (ImportError) **问题**: `ImportError: attempted relative import with no known parent package` **解决方案**: ```bash # 确保以模块方式运行 python -m journal_organizer --help # 而不是直接运行 python main.py # ❌ 错误方式 ``` **原因**: 项目使用相对导入,需要作为包运行。 ### 2. 配置文件问题 **问题**: `ConfigurationError: Missing required configuration` **解决方案**: 1. 检查配置文件是否存在: ```bash ls -la config.yaml ``` 2. 验证配置格式: ```bash python -c " import yaml with open('config.yaml', 'r') as f: config = yaml.safe_load(f) print('配置文件格式正确') " ``` 3. 检查环境变量: ```bash echo $ANTHROPIC_API_KEY echo $OBSIDIAN_API_KEY ``` ### 3. API 连接问题 **问题**: `APIError: Failed to connect to Claude/Obsidian API` **解决方案**: **Claude API**: ```bash # 测试 API 密钥 curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \ -H "Content-Type: application/json" \ https://api.anthropic.com/v1/messages ``` **Obsidian API**: ```bash # 检查 Obsidian Local REST API 插件状态 curl -k -H "Authorization: Bearer $OBSIDIAN_API_KEY" \ https://localhost:27123/ ``` ### 4. 依赖项问题 **问题**: `ModuleNotFoundError: No module named 'xxx'` **解决方案**: ```bash # 检查依赖项状态 python -m journal_organizer check-deps # 安装缺失的依赖项 pip install -r requirements.txt # 安装可选依赖项 pip install pyyaml aiohttp anthropic ``` ### 5. 权限问题 **问题**: `PermissionError: [Errno 13] Permission denied` **解决方案**: ```bash # 检查文件权限 ls -la config.yaml ls -la /path/to/obsidian/vault # 修复权限 chmod 644 config.yaml chmod -R 755 /path/to/obsidian/vault ``` ### 6. SSL 证书问题 **问题**: `SSL: CERTIFICATE_VERIFY_FAILED` **解决方案**: 在配置文件中禁用 SSL 验证(仅用于本地开发): ```yaml obsidian: rest_api: verify_ssl: false ``` ## 调试技巧 ### 1. 启用详细日志 ```bash # 设置调试级别日志 python -m journal_organizer --log-level DEBUG organize ``` ### 2. 使用 Python 调试器 ```python # 在 Skill 中添加断点 import pdb; pdb.set_trace() # 或使用 ipdb(更友好的界面) import ipdb; ipdb.set_trace() ``` ### 3. 检查配置加载 ```python # 测试配置加载 from journal_organizer.main import JournalOrganizerAgent agent = JournalOrganizerAgent("config.yaml") print(f"配置: {agent.config}") ``` ### 4. 测试单个 Skill ```python # 单独测试 Skill import asyncio from journal_organizer.skills.claude_skill import ClaudeAnalyzeSkill from journal_organizer.agent_core import CommandContext async def test_skill(): skill = ClaudeAnalyzeSkill() context = CommandContext(command_name="test") result = await skill.execute(context, text="测试文本") print(f"结果: {result}") asyncio.run(test_skill()) ``` ## 性能问题 ### 1. 内存使用过高 **诊断**: ```python import psutil import os process = psutil.Process(os.getpid()) print(f"内存使用: {process.memory_info().rss / 1024 / 1024:.2f} MB") ``` **解决方案**: - 检查是否有内存泄漏 - 使用 `gc.collect()` 强制垃圾回收 - 限制并发操作数量 ### 2. API 调用缓慢 **诊断**: ```python import time start_time = time.time() result = await api_call() duration = time.time() - start_time print(f"API 调用耗时: {duration:.2f} 秒") ``` **解决方案**: - 检查网络连接 - 增加超时设置 - 使用连接池 - 实现重试机制 ## 错误代码参考 | 错误代码 | 描述 | 解决方案 | |---------|------|----------| | CONFIG_001 | 配置文件不存在 | 创建 config.yaml 文件 | | CONFIG_002 | 配置格式错误 | 检查 YAML/JSON 语法 | | CONFIG_003 | 缺少必需配置项 | 添加缺失的配置项 | | API_001 | API 密钥无效 | 检查并更新 API 密钥 | | API_002 | API 连接超时 | 检查网络连接和服务状态 | | API_003 | API 限流 | 减少请求频率或升级 API 计划 | | SKILL_001 | Skill 执行失败 | 检查 Skill 输入参数和依赖项 | | SKILL_002 | Skill 超时 | 增加超时设置或优化 Skill 逻辑 | ## 获取帮助 如果问题仍然存在: 1. **检查日志文件**: `logs/journal_organizer.log` 2. **运行诊断命令**: `python -m journal_organizer check-deps` 3. **查看详细错误**: 使用 `--log-level DEBUG` 4. **测试基本功能**: 运行简单的命令如 `list` 或 `help` ## 开发环境设置 ### 推荐的开发工具 ```bash # 安装开发依赖 pip install pytest pytest-asyncio black isort mypy # 代码格式化 black . isort . # 类型检查 mypy journal_organizer/ # 运行测试 pytest tests/ ``` ### 调试配置 (VS Code) 创建 `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Debug Journal Organizer", "type": "python", "request": "launch", "module": "journal_organizer", "args": ["--log-level", "DEBUG", "organize"], "console": "integratedTerminal", "cwd": "${workspaceFolder}" } ] } ```