- Add core agent architecture with Command + Skill pattern - Implement Claude API integration for content analysis - Add Obsidian REST API integration for vault operations - Create conversational interface (v2.0) with natural language processing - Add comprehensive configuration management and validation - Include project documentation and developer guides - Set up testing framework with unit, integration, and property tests - Add Kiro specs for Claude API configuration and code quality improvements - Configure project steering files for development guidelines
23 KiB
开发者指南
本指南说明如何扩展和自定义日记整理 Agent,包括新的错误处理模式、配置验证功能和故障排除指南。
架构概览
Agent 系统基于以下核心概念:
- Agent:主控制器,负责管理 Commands 和 Skills
- Command:用户可执行的命令,编排多个 Skills
- Skill:原子化的功能单元,执行具体任务
- SkillChain:多个 Skills 的有序执行链
- ErrorHandler:集中式错误处理和日志记录
- ConfigurationValidator:配置验证和环境变量扩展
核心类
Agent
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 基类:
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 基类:
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:
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
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:
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 方法:
def _register_commands(self) -> None:
"""注册所有命令"""
self.agent.register_command(OrganizeCommand())
self.agent.register_command(MyCommand()) # 添加新命令
步骤 3: 测试新命令
python -m journal_organizer my_command --param1 "value1"
使用 SkillChain
SkillChain 允许您按顺序执行多个 Skills:
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)。这允许并发执行多个操作。
基本示例
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()
错误处理
新的错误处理框架
系统现在使用集中式错误处理框架,提供一致的错误管理和日志记录:
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"]
)
自定义异常类型
使用专门的异常类型来处理不同类型的错误:
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 中实现标准化的错误处理:
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 记录信息:
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)
配置管理
新的配置验证系统
系统现在包含强大的配置验证功能,支持类型检查、环境变量扩展和路径验证:
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}")
环境变量扩展
配置文件支持环境变量扩展:
# 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}"
配置验证示例
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 中访问配置
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)
测试
单元测试示例
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
运行测试
pytest tests/
性能优化
并发执行
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)
缓存
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 进行字符串格式化
# ✅ 推荐:使用 f-strings
name = "用户"
message = f"欢迎 {name},当前时间是 {datetime.now()}"
# ❌ 避免:字符串连接
message = "欢迎 " + name + ",当前时间是 " + str(datetime.now())
2. 使用 pathlib 进行文件路径操作
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 定义数据结构
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. 使用类型提示
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. 使用异步上下文管理器
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()
通用最佳实践
- 单一职责:每个 Skill 只负责一个任务
- 错误处理:使用新的错误处理框架
- 日志记录:使用 logger 记录重要信息
- 配置驱动:使用配置验证系统
- 异步编程:充分利用异步特性提高性能
- 类型安全:使用类型提示和验证
- 文档:为您的 Skills 和 Commands 编写清晰的文档
- 测试:编写单元测试和属性测试确保代码质量
- 版本控制:使用 git 管理代码版本
- 代码格式化:使用 black 和 isort 保持代码风格一致
示例:完整的自定义 Skill
"""
自定义 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="文本统计异常"
)
资源
故障排除指南
本节提供常见问题的解决方案和调试技巧。
常见问题
1. 导入错误 (ImportError)
问题: ImportError: attempted relative import with no known parent package
解决方案:
# 确保以模块方式运行
python -m journal_organizer --help
# 而不是直接运行
python main.py # ❌ 错误方式
原因: 项目使用相对导入,需要作为包运行。
2. 配置文件问题
问题: ConfigurationError: Missing required configuration
解决方案:
- 检查配置文件是否存在:
ls -la config.yaml
- 验证配置格式:
python -c "
import yaml
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
print('配置文件格式正确')
"
- 检查环境变量:
echo $ANTHROPIC_API_KEY
echo $OBSIDIAN_API_KEY
3. API 连接问题
问题: APIError: Failed to connect to Claude/Obsidian API
解决方案:
Claude API:
# 测试 API 密钥
curl -H "Authorization: Bearer $ANTHROPIC_API_KEY" \
-H "Content-Type: application/json" \
https://api.anthropic.com/v1/messages
Obsidian API:
# 检查 Obsidian Local REST API 插件状态
curl -k -H "Authorization: Bearer $OBSIDIAN_API_KEY" \
https://localhost:27123/
4. 依赖项问题
问题: ModuleNotFoundError: No module named 'xxx'
解决方案:
# 检查依赖项状态
python -m journal_organizer check-deps
# 安装缺失的依赖项
pip install -r requirements.txt
# 安装可选依赖项
pip install pyyaml aiohttp anthropic
5. 权限问题
问题: PermissionError: [Errno 13] Permission denied
解决方案:
# 检查文件权限
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 验证(仅用于本地开发):
obsidian:
rest_api:
verify_ssl: false
调试技巧
1. 启用详细日志
# 设置调试级别日志
python -m journal_organizer --log-level DEBUG organize
2. 使用 Python 调试器
# 在 Skill 中添加断点
import pdb; pdb.set_trace()
# 或使用 ipdb(更友好的界面)
import ipdb; ipdb.set_trace()
3. 检查配置加载
# 测试配置加载
from journal_organizer.main import JournalOrganizerAgent
agent = JournalOrganizerAgent("config.yaml")
print(f"配置: {agent.config}")
4. 测试单个 Skill
# 单独测试 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. 内存使用过高
诊断:
import psutil
import os
process = psutil.Process(os.getpid())
print(f"内存使用: {process.memory_info().rss / 1024 / 1024:.2f} MB")
解决方案:
- 检查是否有内存泄漏
- 使用
gc.collect()强制垃圾回收 - 限制并发操作数量
2. API 调用缓慢
诊断:
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 逻辑 |
获取帮助
如果问题仍然存在:
- 检查日志文件:
logs/journal_organizer.log - 运行诊断命令:
python -m journal_organizer check-deps - 查看详细错误: 使用
--log-level DEBUG - 测试基本功能: 运行简单的命令如
list或help
开发环境设置
推荐的开发工具
# 安装开发依赖
pip install pytest pytest-asyncio black isort mypy
# 代码格式化
black .
isort .
# 类型检查
mypy journal_organizer/
# 运行测试
pytest tests/
调试配置 (VS Code)
创建 .vscode/launch.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}"
}
]
}