964 lines
23 KiB
Markdown
964 lines
23 KiB
Markdown
# 开发者指南
|
||||
|
|
|
|||
|
|
本指南说明如何扩展和自定义日记整理 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}"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|