Initial project setup: Obsidian intelligent journal organizer
- 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
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
对话模块
|
||||
提供对话式 Agent 的核心功能
|
||||
"""
|
||||
|
||||
from .intent_understanding import IntentUnderstanding, Intent
|
||||
from .conversation_state import ConversationState, Message, Task, TaskStatus
|
||||
from .response_generator import ResponseGenerator
|
||||
from .conversational_agent import ConversationalAgent, ChatResponse
|
||||
|
||||
__all__ = [
|
||||
"IntentUnderstanding",
|
||||
"Intent",
|
||||
"ConversationState",
|
||||
"Message",
|
||||
"Task",
|
||||
"TaskStatus",
|
||||
"ResponseGenerator",
|
||||
"ConversationalAgent",
|
||||
"ChatResponse",
|
||||
]
|
||||
@@ -0,0 +1,295 @@
|
||||
"""
|
||||
对话状态管理模块
|
||||
管理对话历史、上下文和当前任务状态
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
|
||||
|
||||
class TaskStatus(Enum):
|
||||
"""任务状态"""
|
||||
|
||||
IDLE = "idle" # 空闲
|
||||
PROCESSING = "processing" # 处理中
|
||||
COMPLETED = "completed" # 已完成
|
||||
FAILED = "failed" # 失败
|
||||
WAITING_INPUT = "waiting_input" # 等待用户输入
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""对话消息"""
|
||||
|
||||
role: str # "user" 或 "assistant"
|
||||
content: str
|
||||
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate message after initialization"""
|
||||
if self.role not in ["user", "assistant"]:
|
||||
raise ValueError("role must be 'user' or 'assistant'")
|
||||
if not self.content or not self.content.strip():
|
||||
raise ValueError("content cannot be empty")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
"""当前任务"""
|
||||
|
||||
command: str
|
||||
parameters: Dict[str, Any]
|
||||
status: TaskStatus = TaskStatus.IDLE
|
||||
result: Optional[Any] = None
|
||||
error: Optional[str] = None
|
||||
started_at: Optional[str] = None
|
||||
completed_at: Optional[str] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate task after initialization"""
|
||||
if not self.command or not self.command.strip():
|
||||
raise ValueError("command cannot be empty")
|
||||
if not isinstance(self.parameters, dict):
|
||||
raise ValueError("parameters must be a dictionary")
|
||||
|
||||
|
||||
class ConversationState:
|
||||
"""对话状态管理器"""
|
||||
|
||||
def __init__(self, max_history: int = 20) -> None:
|
||||
"""
|
||||
初始化对话状态
|
||||
|
||||
Args:
|
||||
max_history: 保留的最大历史消息数
|
||||
"""
|
||||
self.logger: logging.Logger = logging.getLogger("ConversationState")
|
||||
self.max_history: int = max_history
|
||||
|
||||
# 对话历史
|
||||
self.messages: List[Message] = []
|
||||
|
||||
# 当前任务
|
||||
self.current_task: Optional[Task] = None
|
||||
|
||||
# 用户偏好和上下文
|
||||
self.user_preferences: Dict[str, Any] = {}
|
||||
self.context: Dict[str, Any] = {}
|
||||
|
||||
# 统计信息
|
||||
self.stats: Dict[str, Union[int, str]] = {
|
||||
"total_messages": 0,
|
||||
"total_tasks": 0,
|
||||
"successful_tasks": 0,
|
||||
"failed_tasks": 0,
|
||||
"session_start": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
def add_message(
|
||||
self, role: str, content: str, metadata: Optional[Dict[str, Any]] = None
|
||||
) -> Message:
|
||||
"""
|
||||
添加消息到历史
|
||||
|
||||
Args:
|
||||
role: 消息角色("user" 或 "assistant")
|
||||
content: 消息内容
|
||||
metadata: 消息元数据
|
||||
|
||||
Returns:
|
||||
Message: 添加的消息
|
||||
"""
|
||||
message: Message = Message(role=role, content=content, metadata=metadata or {})
|
||||
|
||||
self.messages.append(message)
|
||||
self.stats["total_messages"] += 1
|
||||
|
||||
# 保持历史长度在限制内
|
||||
if len(self.messages) > self.max_history:
|
||||
self.messages.pop(0)
|
||||
|
||||
self.logger.debug(f"添加消息: {role} - {content[:50]}...")
|
||||
|
||||
return message
|
||||
|
||||
def get_recent_messages(self, count: int = 5) -> List[Message]:
|
||||
"""
|
||||
获取最近的 N 条消息
|
||||
|
||||
Args:
|
||||
count: 消息数量
|
||||
|
||||
Returns:
|
||||
最近的消息列表
|
||||
"""
|
||||
return self.messages[-count:]
|
||||
|
||||
def get_conversation_history(self) -> List[Dict[str, str]]:
|
||||
"""
|
||||
获取对话历史(用于 Claude API)
|
||||
|
||||
Returns:
|
||||
对话历史列表
|
||||
"""
|
||||
return [{"role": msg.role, "content": msg.content} for msg in self.messages]
|
||||
|
||||
def start_task(self, command: str, parameters: Dict[str, Any]) -> Task:
|
||||
"""
|
||||
开始一个新任务
|
||||
|
||||
Args:
|
||||
command: 命令名称
|
||||
parameters: 命令参数
|
||||
|
||||
Returns:
|
||||
Task: 创建的任务
|
||||
"""
|
||||
self.current_task = Task(
|
||||
command=command,
|
||||
parameters=parameters,
|
||||
status=TaskStatus.PROCESSING,
|
||||
started_at=datetime.now().isoformat(),
|
||||
)
|
||||
|
||||
self.stats["total_tasks"] += 1
|
||||
self.logger.info(f"开始任务: {command} - {parameters}")
|
||||
|
||||
return self.current_task
|
||||
|
||||
def complete_task(self, result: Any) -> Optional[Task]:
|
||||
"""
|
||||
完成当前任务
|
||||
|
||||
Args:
|
||||
result: 任务结果
|
||||
|
||||
Returns:
|
||||
Task: 完成的任务
|
||||
"""
|
||||
if not self.current_task:
|
||||
self.logger.warning("没有正在进行的任务")
|
||||
return None
|
||||
|
||||
self.current_task.status = TaskStatus.COMPLETED
|
||||
self.current_task.result = result
|
||||
self.current_task.completed_at = datetime.now().isoformat()
|
||||
self.stats["successful_tasks"] += 1
|
||||
|
||||
self.logger.info(f"任务完成: {self.current_task.command}")
|
||||
|
||||
return self.current_task
|
||||
|
||||
def fail_task(self, error: str) -> Optional[Task]:
|
||||
"""
|
||||
标记任务失败
|
||||
|
||||
Args:
|
||||
error: 错误信息
|
||||
|
||||
Returns:
|
||||
Task: 失败的任务
|
||||
"""
|
||||
if not self.current_task:
|
||||
self.logger.warning("没有正在进行的任务")
|
||||
return None
|
||||
|
||||
self.current_task.status = TaskStatus.FAILED
|
||||
self.current_task.error = error
|
||||
self.current_task.completed_at = datetime.now().isoformat()
|
||||
self.stats["failed_tasks"] += 1
|
||||
|
||||
self.logger.error(f"任务失败: {self.current_task.command} - {error}")
|
||||
|
||||
return self.current_task
|
||||
|
||||
def set_context(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
设置上下文信息
|
||||
|
||||
Args:
|
||||
key: 上下文键
|
||||
value: 上下文值
|
||||
"""
|
||||
self.context[key] = value
|
||||
self.logger.debug(f"设置上下文: {key} = {value}")
|
||||
|
||||
def get_context(self, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
获取上下文信息
|
||||
|
||||
Args:
|
||||
key: 上下文键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
上下文值
|
||||
"""
|
||||
return self.context.get(key, default)
|
||||
|
||||
def set_preference(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
设置用户偏好
|
||||
|
||||
Args:
|
||||
key: 偏好键
|
||||
value: 偏好值
|
||||
"""
|
||||
self.user_preferences[key] = value
|
||||
self.logger.debug(f"设置偏好: {key} = {value}")
|
||||
|
||||
def get_preference(self, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
获取用户偏好
|
||||
|
||||
Args:
|
||||
key: 偏好键
|
||||
default: 默认值
|
||||
|
||||
Returns:
|
||||
偏好值
|
||||
"""
|
||||
return self.user_preferences.get(key, default)
|
||||
|
||||
def get_summary(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取对话状态摘要
|
||||
|
||||
Returns:
|
||||
状态摘要字典
|
||||
"""
|
||||
return {
|
||||
"total_messages": len(self.messages),
|
||||
"recent_messages": [
|
||||
{
|
||||
"role": msg.role,
|
||||
"content": msg.content[:100],
|
||||
"timestamp": msg.timestamp,
|
||||
}
|
||||
for msg in self.get_recent_messages(3)
|
||||
],
|
||||
"current_task": {
|
||||
"command": self.current_task.command,
|
||||
"status": self.current_task.status.value,
|
||||
"started_at": self.current_task.started_at,
|
||||
}
|
||||
if self.current_task
|
||||
else None,
|
||||
"stats": self.stats,
|
||||
"preferences": self.user_preferences,
|
||||
}
|
||||
|
||||
def clear_history(self) -> None:
|
||||
"""清除对话历史"""
|
||||
self.messages.clear()
|
||||
self.logger.info("对话历史已清除")
|
||||
|
||||
def reset(self) -> None:
|
||||
"""重置对话状态"""
|
||||
self.messages.clear()
|
||||
self.current_task = None
|
||||
self.context.clear()
|
||||
self.logger.info("对话状态已重置")
|
||||
@@ -0,0 +1,218 @@
|
||||
"""
|
||||
对话式 Agent 核心模块
|
||||
融合对话能力的智能 Agent
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
from .conversation_state import ConversationState
|
||||
from .intent_understanding import IntentUnderstanding, Intent
|
||||
from .response_generator import ResponseGenerator
|
||||
from ..agent_core import Agent, SkillResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChatResponse:
|
||||
"""对话响应"""
|
||||
|
||||
message: str # 响应消息
|
||||
suggestions: Optional[List[str]] = None # 建议的后续操作
|
||||
status: str = "success" # 状态:success, error, waiting_input
|
||||
metadata: Optional[Dict[str, Any]] = None # 元数据
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate response after initialization"""
|
||||
if not self.message or not self.message.strip():
|
||||
raise ValueError("message cannot be empty")
|
||||
valid_statuses = ["success", "error", "waiting_input"]
|
||||
if self.status not in valid_statuses:
|
||||
raise ValueError(f"status must be one of {valid_statuses}")
|
||||
if self.suggestions is not None and not isinstance(self.suggestions, list):
|
||||
raise ValueError("suggestions must be a list or None")
|
||||
|
||||
|
||||
class ConversationalAgent:
|
||||
"""对话式 Agent,融合对话能力的智能 Agent"""
|
||||
|
||||
def __init__(
|
||||
self, command_agent: Agent, config: Optional[Dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""
|
||||
初始化对话式 Agent
|
||||
|
||||
Args:
|
||||
command_agent: 底层的 Command Agent
|
||||
config: 配置字典
|
||||
"""
|
||||
self.command_agent: Agent = command_agent
|
||||
self.config: Dict[str, Any] = config or {}
|
||||
self.logger: logging.Logger = logging.getLogger("ConversationalAgent")
|
||||
|
||||
# 初始化各个模块
|
||||
self.intent_understanding: IntentUnderstanding = IntentUnderstanding(config)
|
||||
self.conversation_state: ConversationState = ConversationState()
|
||||
self.response_generator: ResponseGenerator = ResponseGenerator(config)
|
||||
|
||||
async def initialize(self) -> str:
|
||||
"""
|
||||
初始化 Agent 并返回欢迎消息
|
||||
|
||||
Returns:
|
||||
欢迎消息
|
||||
"""
|
||||
welcome_msg = await self.response_generator.generate_welcome_message()
|
||||
self.conversation_state.add_message("assistant", welcome_msg)
|
||||
self.logger.info("对话式 Agent 已初始化")
|
||||
return welcome_msg
|
||||
|
||||
async def chat(self, user_message: str) -> ChatResponse:
|
||||
"""
|
||||
处理用户消息并返回响应
|
||||
|
||||
Args:
|
||||
user_message: 用户的输入消息
|
||||
|
||||
Returns:
|
||||
ChatResponse: 对话响应
|
||||
"""
|
||||
self.logger.info(f"处理用户消息: {user_message}")
|
||||
|
||||
try:
|
||||
# 1. 记录用户消息
|
||||
self.conversation_state.add_message("user", user_message)
|
||||
|
||||
# 2. 理解用户意图
|
||||
context: Dict[str, Any] = {
|
||||
"conversation_history": self.conversation_state.get_conversation_history()
|
||||
}
|
||||
intent: Intent = await self.intent_understanding.understand(
|
||||
user_message, context
|
||||
)
|
||||
|
||||
self.logger.debug(f"识别的意图: {intent.command}")
|
||||
|
||||
# 3. 如果需要澄清,返回澄清问题
|
||||
if intent.clarification_needed:
|
||||
response: ChatResponse = ChatResponse(
|
||||
message=intent.clarification_question or "抱歉,我没有理解您的意思。能否请您重新表述?",
|
||||
status="waiting_input",
|
||||
)
|
||||
self.conversation_state.add_message("assistant", response.message)
|
||||
return response
|
||||
|
||||
# 4. 映射意图到命令并执行
|
||||
command_result: SkillResult = await self._execute_command(intent)
|
||||
|
||||
# 5. 生成响应
|
||||
if command_result.success:
|
||||
response_msg: str = (
|
||||
await self.response_generator.generate_success_response(
|
||||
intent.command, command_result.data, user_message
|
||||
)
|
||||
)
|
||||
else:
|
||||
response_msg = await self.response_generator.generate_error_response(
|
||||
intent.command, command_result.error or "未知错误", user_message
|
||||
)
|
||||
|
||||
# 6. 生成建议
|
||||
suggestions: List[str] = await self.response_generator.generate_suggestions(
|
||||
self.conversation_state.context
|
||||
)
|
||||
|
||||
# 7. 构建响应
|
||||
response = ChatResponse(
|
||||
message=response_msg,
|
||||
suggestions=suggestions,
|
||||
status="success" if command_result.success else "error",
|
||||
metadata={
|
||||
"command": intent.command,
|
||||
"confidence": intent.confidence,
|
||||
"reasoning": intent.raw_understanding,
|
||||
},
|
||||
)
|
||||
|
||||
# 8. 记录助手响应
|
||||
self.conversation_state.add_message("assistant", response_msg)
|
||||
|
||||
self.logger.info(f"响应生成完成: {response_msg[:50]}...")
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"处理消息失败: {str(e)}", exc_info=True)
|
||||
|
||||
error_response: ChatResponse = ChatResponse(
|
||||
message="抱歉,处理您的请求时出现了问题。请稍后重试。", status="error"
|
||||
)
|
||||
|
||||
self.conversation_state.add_message("assistant", error_response.message)
|
||||
|
||||
return error_response
|
||||
|
||||
async def _execute_command(self, intent: Intent) -> SkillResult:
|
||||
"""
|
||||
执行命令
|
||||
|
||||
Args:
|
||||
intent: 识别的意图
|
||||
|
||||
Returns:
|
||||
SkillResult: 命令执行结果
|
||||
"""
|
||||
try:
|
||||
# 开始任务
|
||||
task = self.conversation_state.start_task(intent.command, intent.parameters)
|
||||
|
||||
# 执行命令
|
||||
result: SkillResult = await self.command_agent.execute_command(
|
||||
intent.command, intent.parameters
|
||||
)
|
||||
|
||||
# 更新任务状态
|
||||
if result.success:
|
||||
self.conversation_state.complete_task(result.data)
|
||||
else:
|
||||
self.conversation_state.fail_task(result.error or "未知错误")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"命令执行失败: {str(e)}")
|
||||
self.conversation_state.fail_task(str(e))
|
||||
|
||||
return SkillResult(success=False, error=str(e), message="命令执行失败")
|
||||
|
||||
def get_conversation_history(self) -> List[Dict[str, str]]:
|
||||
"""
|
||||
获取对话历史
|
||||
|
||||
Returns:
|
||||
对话历史列表
|
||||
"""
|
||||
return self.conversation_state.get_conversation_history()
|
||||
|
||||
def get_state_summary(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取对话状态摘要
|
||||
|
||||
Returns:
|
||||
状态摘要
|
||||
"""
|
||||
return self.conversation_state.get_summary()
|
||||
|
||||
def clear_history(self) -> None:
|
||||
"""
|
||||
清除对话历史
|
||||
"""
|
||||
self.conversation_state.clear_history()
|
||||
self.logger.info("对话历史已清除")
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
重置对话状态
|
||||
"""
|
||||
self.conversation_state.reset()
|
||||
self.logger.info("对话状态已重置")
|
||||
@@ -0,0 +1,402 @@
|
||||
"""
|
||||
意图理解模块
|
||||
使用 Claude 理解用户的自然语言输入,提取意图和参数
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
from ..dependency_manager import get_dependency_manager, graceful_import
|
||||
|
||||
# Try to import anthropic with graceful degradation
|
||||
dependency_manager = get_dependency_manager()
|
||||
Anthropic = dependency_manager.get_class_from_module('anthropic', 'Anthropic')
|
||||
|
||||
|
||||
@dataclass
|
||||
class Intent:
|
||||
"""用户意图"""
|
||||
|
||||
command: str # 对应的命令名称
|
||||
parameters: Dict[str, Any] = field(default_factory=dict)
|
||||
confidence: float = 1.0
|
||||
clarification_needed: bool = False
|
||||
clarification_question: Optional[str] = None
|
||||
raw_understanding: str = "" # Claude 的原始理解
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate intent after initialization"""
|
||||
if not self.command or not self.command.strip():
|
||||
raise ValueError("command cannot be empty")
|
||||
if not 0.0 <= self.confidence <= 1.0:
|
||||
raise ValueError("confidence must be between 0.0 and 1.0")
|
||||
if not isinstance(self.parameters, dict):
|
||||
raise ValueError("parameters must be a dictionary")
|
||||
|
||||
|
||||
class IntentUnderstanding:
|
||||
"""意图理解器,使用 Claude 理解用户意图"""
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
|
||||
"""
|
||||
初始化意图理解器
|
||||
|
||||
Args:
|
||||
config: 配置字典
|
||||
"""
|
||||
self.config: Dict[str, Any] = config or {}
|
||||
self.logger: logging.Logger = logging.getLogger("IntentUnderstanding")
|
||||
|
||||
# Initialize Anthropic client with dependency checking
|
||||
if Anthropic is not None:
|
||||
try:
|
||||
self.client: Optional[Anthropic] = Anthropic()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to initialize Anthropic client: {e}")
|
||||
self.client = None
|
||||
else:
|
||||
self.client = None
|
||||
self.logger.warning("Anthropic library not available - Claude-based understanding disabled")
|
||||
|
||||
# 定义支持的命令和它们的关键词
|
||||
self.command_keywords: Dict[str, List[str]] = {
|
||||
"organize": [
|
||||
"整理",
|
||||
"组织",
|
||||
"分类",
|
||||
"归纳",
|
||||
"整理日记",
|
||||
"organize",
|
||||
"arrange",
|
||||
"categorize",
|
||||
],
|
||||
"analyze": [
|
||||
"分析",
|
||||
"总结",
|
||||
"统计",
|
||||
"分类",
|
||||
"分析日记",
|
||||
"analyze",
|
||||
"summarize",
|
||||
"statistics",
|
||||
],
|
||||
"export": [
|
||||
"导出",
|
||||
"保存",
|
||||
"生成",
|
||||
"输出",
|
||||
"导出为",
|
||||
"export",
|
||||
"save",
|
||||
"generate",
|
||||
"output",
|
||||
],
|
||||
"review": [
|
||||
"回顾",
|
||||
"查看",
|
||||
"查询",
|
||||
"搜索",
|
||||
"浏览",
|
||||
"review",
|
||||
"view",
|
||||
"search",
|
||||
"browse",
|
||||
],
|
||||
}
|
||||
|
||||
# 定义参数提取规则
|
||||
self.parameter_patterns: Dict[str, List[str]] = {
|
||||
"date": [
|
||||
r"(\d{4}[-/]\d{1,2}[-/]\d{1,2})", # YYYY-MM-DD 或 YYYY/M/D
|
||||
r"(今天|明天|昨天|前天)", # 相对日期
|
||||
r"(这周|本周|上周|下周)", # 周
|
||||
r"(这个月|本月|上个月|下个月)", # 月
|
||||
],
|
||||
"category": [
|
||||
r"(经验|教训|待办|问题|成就|改进)",
|
||||
r"(experience|lesson|task|problem|achievement|improvement)",
|
||||
],
|
||||
"format": [r"(PDF|Excel|Word|Markdown|JSON)", r"(pdf|xlsx|docx|md|json)"],
|
||||
}
|
||||
|
||||
async def understand(
|
||||
self, user_message: str, context: Optional[Dict[str, Any]] = None
|
||||
) -> Intent:
|
||||
"""
|
||||
理解用户的自然语言输入
|
||||
|
||||
Args:
|
||||
user_message: 用户的输入消息
|
||||
context: 上下文信息(如对话历史)
|
||||
|
||||
Returns:
|
||||
Intent: 提取的意图
|
||||
"""
|
||||
self.logger.debug(f"理解用户消息: {user_message}")
|
||||
|
||||
try:
|
||||
# 首先尝试本地模式匹配(快速路径)
|
||||
intent = self._match_intent_locally(user_message)
|
||||
if intent and intent.confidence > 0.8:
|
||||
self.logger.debug(f"本地匹配成功: {intent.command}")
|
||||
return intent
|
||||
|
||||
# 使用 Claude 进行更深入的理解
|
||||
intent = await self._understand_with_claude(user_message, context)
|
||||
|
||||
return intent
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"意图理解失败: {str(e)}")
|
||||
return Intent(
|
||||
command="unknown",
|
||||
clarification_needed=True,
|
||||
clarification_question="抱歉,我没有理解您的意思。能否请您重新表述?",
|
||||
)
|
||||
|
||||
def _match_intent_locally(self, user_message: str) -> Optional[Intent]:
|
||||
"""
|
||||
本地模式匹配,快速识别常见意图
|
||||
|
||||
Args:
|
||||
user_message: 用户消息
|
||||
|
||||
Returns:
|
||||
Intent 或 None
|
||||
"""
|
||||
message_lower: str = user_message.lower()
|
||||
|
||||
# 逐个检查命令关键词
|
||||
for command, keywords in self.command_keywords.items():
|
||||
for keyword in keywords:
|
||||
if keyword in message_lower:
|
||||
# 提取参数
|
||||
parameters: Dict[str, Any] = self._extract_parameters_locally(
|
||||
user_message
|
||||
)
|
||||
|
||||
return Intent(
|
||||
command=command, parameters=parameters, confidence=0.9
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _extract_parameters_locally(self, user_message: str) -> Dict[str, Any]:
|
||||
"""
|
||||
本地提取参数
|
||||
|
||||
Args:
|
||||
user_message: 用户消息
|
||||
|
||||
Returns:
|
||||
提取的参数字典
|
||||
"""
|
||||
parameters: Dict[str, Any] = {}
|
||||
|
||||
# 提取日期
|
||||
for pattern in self.parameter_patterns["date"]:
|
||||
match: Optional[re.Match[str]] = re.search(pattern, user_message)
|
||||
if match:
|
||||
date_str: str = match.group(1)
|
||||
parameters["date"] = self._normalize_date(date_str)
|
||||
break
|
||||
|
||||
# 提取分类
|
||||
for pattern in self.parameter_patterns["category"]:
|
||||
match = re.search(pattern, user_message)
|
||||
if match:
|
||||
parameters["category"] = match.group(1)
|
||||
break
|
||||
|
||||
# 提取格式
|
||||
for pattern in self.parameter_patterns["format"]:
|
||||
match = re.search(pattern, user_message)
|
||||
if match:
|
||||
parameters["format"] = match.group(1).lower()
|
||||
break
|
||||
|
||||
return parameters
|
||||
|
||||
def _normalize_date(self, date_str: str) -> str:
|
||||
"""
|
||||
规范化日期字符串为 YYYY-MM-DD 格式
|
||||
|
||||
Args:
|
||||
date_str: 日期字符串
|
||||
|
||||
Returns:
|
||||
规范化的日期字符串
|
||||
"""
|
||||
today: datetime = datetime.now()
|
||||
|
||||
# 处理相对日期
|
||||
if date_str == "今天":
|
||||
return today.strftime("%Y-%m-%d")
|
||||
elif date_str == "明天":
|
||||
return (today + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
elif date_str == "昨天":
|
||||
return (today - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
elif date_str == "前天":
|
||||
return (today - timedelta(days=2)).strftime("%Y-%m-%d")
|
||||
|
||||
# 处理标准日期格式
|
||||
try:
|
||||
# 尝试 YYYY-MM-DD 或 YYYY/M/D 格式
|
||||
for fmt in ["%Y-%m-%d", "%Y/%m/%d", "%Y-%m-%d"]:
|
||||
try:
|
||||
parsed: datetime = datetime.strptime(
|
||||
date_str.replace("/", "-"), fmt
|
||||
)
|
||||
return parsed.strftime("%Y-%m-%d")
|
||||
except ValueError:
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
return date_str
|
||||
|
||||
async def _understand_with_claude(
|
||||
self, user_message: str, context: Optional[Dict[str, Any]] = None
|
||||
) -> Intent:
|
||||
"""
|
||||
使用 Claude 理解用户意图
|
||||
|
||||
Args:
|
||||
user_message: 用户消息
|
||||
context: 上下文信息
|
||||
|
||||
Returns:
|
||||
Intent: 提取的意图
|
||||
"""
|
||||
# Check if Claude is available
|
||||
if self.client is None:
|
||||
self.logger.warning("Claude client not available, falling back to local matching")
|
||||
return Intent(
|
||||
command="unknown",
|
||||
clarification_needed=True,
|
||||
clarification_question="抱歉,AI 理解功能暂时不可用。请使用更具体的命令,如 '整理今天的日记' 或 '分析本周内容'。",
|
||||
)
|
||||
|
||||
# Construct prompt
|
||||
prompt = self._build_understanding_prompt(user_message, context)
|
||||
|
||||
try:
|
||||
# Call Claude
|
||||
response = self.client.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=500,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
|
||||
# Parse response
|
||||
response_text = response.content[0].text
|
||||
self.logger.debug(f"Claude 响应: {response_text}")
|
||||
|
||||
return self._parse_claude_response(response_text, user_message)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Claude API call failed: {str(e)}")
|
||||
# Fall back to local matching
|
||||
local_intent = self._match_intent_locally(user_message)
|
||||
if local_intent:
|
||||
return local_intent
|
||||
|
||||
return Intent(
|
||||
command="unknown",
|
||||
clarification_needed=True,
|
||||
clarification_question="抱歉,我在理解您的意图时遇到了问题。请尝试使用更具体的命令。",
|
||||
)
|
||||
|
||||
def _build_understanding_prompt(
|
||||
self, user_message: str, context: Optional[Dict[str, Any]] = None
|
||||
) -> str:
|
||||
"""
|
||||
构建用于 Claude 的提示
|
||||
|
||||
Args:
|
||||
user_message: 用户消息
|
||||
context: 上下文信息
|
||||
|
||||
Returns:
|
||||
提示文本
|
||||
"""
|
||||
available_commands: str = ", ".join(self.command_keywords.keys())
|
||||
|
||||
prompt: str = f"""你是一个 Obsidian 日记整理助手的意图识别器。
|
||||
|
||||
用户消息: "{user_message}"
|
||||
|
||||
可用的命令有: {available_commands}
|
||||
|
||||
请分析用户的意图,并返回一个 JSON 对象,包含以下字段:
|
||||
|
||||
{{
|
||||
"command": "识别出的命令名称(必须是可用命令之一)",
|
||||
"parameters": {{
|
||||
"date": "如果用户指定了日期,转换为 YYYY-MM-DD 格式;否则为 null",
|
||||
"category": "如果用户指定了分类,提取分类名称;否则为 null",
|
||||
"format": "如果用户指定了导出格式,提取格式;否则为 null",
|
||||
"other_params": "其他相关参数"
|
||||
}},
|
||||
"confidence": 0.0 到 1.0 之间的置信度,
|
||||
"clarification_needed": 是否需要澄清(布尔值),
|
||||
"clarification_question": "如果需要澄清,提出的问题;否则为 null",
|
||||
"reasoning": "简短的推理说明"
|
||||
}}
|
||||
|
||||
请确保返回有效的 JSON 格式。"""
|
||||
|
||||
if context and "conversation_history" in context:
|
||||
prompt += f"\n\n对话历史(最近的消息):\n"
|
||||
for msg in context["conversation_history"][-3:]:
|
||||
prompt += f"- {msg['role']}: {msg['content']}\n"
|
||||
|
||||
return prompt
|
||||
|
||||
def _parse_claude_response(self, response_text: str, user_message: str) -> Intent:
|
||||
"""
|
||||
解析 Claude 的响应
|
||||
|
||||
Args:
|
||||
response_text: Claude 的响应文本
|
||||
user_message: 原始用户消息
|
||||
|
||||
Returns:
|
||||
Intent: 提取的意图
|
||||
"""
|
||||
try:
|
||||
# 尝试从响应中提取 JSON
|
||||
json_match: Optional[re.Match[str]] = re.search(
|
||||
r"\{.*\}", response_text, re.DOTALL
|
||||
)
|
||||
if not json_match:
|
||||
raise ValueError("未找到 JSON 响应")
|
||||
|
||||
json_str: str = json_match.group(0)
|
||||
data: Dict[str, Any] = json.loads(json_str)
|
||||
|
||||
# 构建 Intent 对象
|
||||
intent: Intent = Intent(
|
||||
command=data.get("command", "unknown"),
|
||||
parameters={
|
||||
k: v for k, v in data.get("parameters", {}).items() if v is not None
|
||||
},
|
||||
confidence=data.get("confidence", 0.7),
|
||||
clarification_needed=data.get("clarification_needed", False),
|
||||
clarification_question=data.get("clarification_question"),
|
||||
raw_understanding=data.get("reasoning", ""),
|
||||
)
|
||||
|
||||
return intent
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"解析 Claude 响应失败: {str(e)}")
|
||||
return Intent(
|
||||
command="unknown",
|
||||
clarification_needed=True,
|
||||
clarification_question="抱歉,我在处理您的请求时遇到了问题。能否请您重新表述?",
|
||||
)
|
||||
@@ -0,0 +1,234 @@
|
||||
"""
|
||||
响应生成模块
|
||||
使用 Claude 生成自然语言响应
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
from ..dependency_manager import get_dependency_manager
|
||||
|
||||
# Try to import anthropic with graceful degradation
|
||||
dependency_manager = get_dependency_manager()
|
||||
Anthropic = dependency_manager.get_class_from_module('anthropic', 'Anthropic')
|
||||
|
||||
|
||||
class ResponseGenerator:
|
||||
"""响应生成器,使用 Claude 生成自然语言响应"""
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
|
||||
"""
|
||||
初始化响应生成器
|
||||
|
||||
Args:
|
||||
config: 配置字典
|
||||
"""
|
||||
self.config: Dict[str, Any] = config or {}
|
||||
self.logger: logging.Logger = logging.getLogger("ResponseGenerator")
|
||||
|
||||
# Initialize Anthropic client with dependency checking
|
||||
if Anthropic is not None:
|
||||
try:
|
||||
self.client: Optional[Anthropic] = Anthropic()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to initialize Anthropic client: {e}")
|
||||
self.client = None
|
||||
else:
|
||||
self.client = None
|
||||
self.logger.warning("Anthropic library not available - AI response generation disabled")
|
||||
|
||||
async def generate_success_response(
|
||||
self, command: str, result: Any, user_message: str
|
||||
) -> str:
|
||||
"""
|
||||
生成成功响应
|
||||
|
||||
Args:
|
||||
command: 执行的命令
|
||||
result: 命令执行结果
|
||||
user_message: 原始用户消息
|
||||
|
||||
Returns:
|
||||
生成的响应文本
|
||||
"""
|
||||
prompt: str = self._build_success_prompt(command, result, user_message)
|
||||
return await self._generate_response(prompt)
|
||||
|
||||
async def generate_error_response(
|
||||
self, command: str, error: str, user_message: str
|
||||
) -> str:
|
||||
"""
|
||||
生成错误响应
|
||||
|
||||
Args:
|
||||
command: 执行的命令
|
||||
error: 错误信息
|
||||
user_message: 原始用户消息
|
||||
|
||||
Returns:
|
||||
生成的响应文本
|
||||
"""
|
||||
prompt: str = self._build_error_prompt(command, error, user_message)
|
||||
return await self._generate_response(prompt)
|
||||
|
||||
async def generate_clarification_response(self, question: str) -> str:
|
||||
"""
|
||||
生成澄清问题的响应
|
||||
|
||||
Args:
|
||||
question: 澄清问题
|
||||
|
||||
Returns:
|
||||
生成的响应文本
|
||||
"""
|
||||
return question
|
||||
|
||||
async def generate_welcome_message(self) -> str:
|
||||
"""
|
||||
生成欢迎消息
|
||||
|
||||
Returns:
|
||||
欢迎消息
|
||||
"""
|
||||
return "👋 欢迎使用 Obsidian 日记整理助手!我可以帮您整理日记、分析内容、导出总结等。请告诉我您想要做什么?"
|
||||
|
||||
async def generate_suggestions(
|
||||
self, context: Optional[Dict[str, Any]] = None
|
||||
) -> List[str]:
|
||||
"""
|
||||
生成智能建议
|
||||
|
||||
Args:
|
||||
context: 上下文信息
|
||||
|
||||
Returns:
|
||||
建议列表
|
||||
"""
|
||||
suggestions: List[str] = ["整理今天的日记", "分析本周的主题", "导出月度总结", "查看最近的经验"]
|
||||
|
||||
# 可以根据上下文生成更个性化的建议
|
||||
if context:
|
||||
# 例如,如果是周五,建议生成周总结
|
||||
from datetime import datetime
|
||||
|
||||
if datetime.now().weekday() == 4: # 周五
|
||||
suggestions.insert(0, "生成本周总结")
|
||||
|
||||
return suggestions
|
||||
|
||||
def _build_success_prompt(
|
||||
self, command: str, result: Any, user_message: str
|
||||
) -> str:
|
||||
"""
|
||||
构建成功响应的提示
|
||||
|
||||
Args:
|
||||
command: 执行的命令
|
||||
result: 命令执行结果
|
||||
user_message: 原始用户消息
|
||||
|
||||
Returns:
|
||||
提示文本
|
||||
"""
|
||||
result_str: str = self._format_result(result)
|
||||
|
||||
prompt: str = f"""你是一个友好的 Obsidian 日记整理助手。
|
||||
|
||||
用户问: "{user_message}"
|
||||
|
||||
你已经成功执行了 "{command}" 命令。
|
||||
|
||||
执行结果:
|
||||
{result_str}
|
||||
|
||||
请用友好、自然的语言总结结果。保持回复简洁(1-3 句话)。
|
||||
如果有重要的数据或统计信息,请突出显示。
|
||||
|
||||
示例回复:
|
||||
- "✓ 已成功整理您今天的日记。提取了 5 条经验、3 条待办事项和 2 个问题。"
|
||||
- "✓ 分析完成!本周的主题主要集中在项目管理和技术学习两个方面。"
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
def _build_error_prompt(self, command: str, error: str, user_message: str) -> str:
|
||||
"""
|
||||
构建错误响应的提示
|
||||
|
||||
Args:
|
||||
command: 执行的命令
|
||||
error: 错误信息
|
||||
user_message: 原始用户消息
|
||||
|
||||
Returns:
|
||||
提示文本
|
||||
"""
|
||||
prompt: str = f"""你是一个友好的 Obsidian 日记整理助手。
|
||||
|
||||
用户问: "{user_message}"
|
||||
|
||||
执行 "{command}" 命令时出现了错误:
|
||||
{error}
|
||||
|
||||
请用友好、有帮助的语言解释错误,并建议可能的解决方案。保持回复简洁(1-2 句话)。
|
||||
|
||||
示例回复:
|
||||
- "✗ 抱歉,找不到该日期的日记。请检查日期格式是否正确(YYYY-MM-DD)。"
|
||||
- "✗ 执行过程中出现了问题。请稍后重试,或检查您的配置设置。"
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
def _format_result(self, result: Any) -> str:
|
||||
"""
|
||||
格式化结果
|
||||
|
||||
Args:
|
||||
result: 结果对象
|
||||
|
||||
Returns:
|
||||
格式化的结果字符串
|
||||
"""
|
||||
if isinstance(result, dict):
|
||||
lines: List[str] = []
|
||||
for key, value in result.items():
|
||||
if isinstance(value, (list, dict)):
|
||||
lines.append(f"- {key}: {len(value)} 项")
|
||||
else:
|
||||
lines.append(f"- {key}: {value}")
|
||||
return "\n".join(lines)
|
||||
elif isinstance(result, list):
|
||||
return "\n".join([f"- {item}" for item in result])
|
||||
else:
|
||||
return str(result)
|
||||
|
||||
async def _generate_response(self, prompt: str) -> str:
|
||||
"""
|
||||
使用 Claude 生成响应
|
||||
|
||||
Args:
|
||||
prompt: 提示文本
|
||||
|
||||
Returns:
|
||||
生成的响应
|
||||
"""
|
||||
# Check if Claude is available
|
||||
if self.client is None:
|
||||
self.logger.warning("Claude client not available, using fallback response")
|
||||
return "✓ 操作已完成。(注意:AI 响应生成功能暂时不可用)"
|
||||
|
||||
try:
|
||||
response = self.client.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=300,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
)
|
||||
|
||||
response_text: str = response.content[0].text.strip()
|
||||
self.logger.debug(f"生成响应: {response_text[:100]}...")
|
||||
|
||||
return response_text
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"生成响应失败: {str(e)}")
|
||||
return "✓ 操作已完成。(注意:AI 响应生成遇到问题,请检查网络连接和 API 配置)"
|
||||
Reference in New Issue
Block a user