Merge branch 'develop' into fix/workflow
This commit is contained in:
@@ -167,7 +167,6 @@ class Settings:
|
||||
|
||||
# official environment system version
|
||||
SYSTEM_VERSION: str = os.getenv("SYSTEM_VERSION", "v0.2.0")
|
||||
SYSTEM_INTRODUCTION: str = os.getenv("SYSTEM_INTRODUCTION", "")
|
||||
|
||||
def get_memory_output_path(self, filename: str = "") -> str:
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
from app.core.logging_config import get_memory_logger
|
||||
@@ -28,6 +29,118 @@ class MemorySummaryResponse(RobustLLMResponse):
|
||||
)
|
||||
|
||||
|
||||
async def generate_title_and_type_for_summary(
|
||||
content: str,
|
||||
llm_client
|
||||
) -> Tuple[str, str]:
|
||||
"""
|
||||
为MemorySummary生成标题和类型
|
||||
|
||||
此方法应该在创建MemorySummary节点时调用,生成title和type
|
||||
|
||||
Args:
|
||||
content: Summary的内容文本
|
||||
llm_client: LLM客户端实例
|
||||
|
||||
Returns:
|
||||
(标题, 类型)元组
|
||||
"""
|
||||
from app.core.memory.utils.prompt.prompt_utils import render_episodic_title_and_type_prompt
|
||||
|
||||
# 定义有效的类型集合
|
||||
VALID_TYPES = {
|
||||
"conversation", # 对话
|
||||
"project_work", # 项目/工作
|
||||
"learning", # 学习
|
||||
"decision", # 决策
|
||||
"important_event" # 重要事件
|
||||
}
|
||||
DEFAULT_TYPE = "conversation" # 默认类型
|
||||
|
||||
try:
|
||||
if not content:
|
||||
logger.warning("content为空,无法生成标题和类型")
|
||||
return ("空内容", DEFAULT_TYPE)
|
||||
|
||||
# 1. 渲染Jinja2提示词模板
|
||||
prompt = await render_episodic_title_and_type_prompt(content)
|
||||
|
||||
# 2. 调用LLM生成标题和类型
|
||||
messages = [
|
||||
{"role": "user", "content": prompt}
|
||||
]
|
||||
|
||||
response = await llm_client.chat(messages=messages)
|
||||
|
||||
# 3. 解析LLM响应
|
||||
content_response = response.content
|
||||
if isinstance(content_response, list):
|
||||
if len(content_response) > 0:
|
||||
if isinstance(content_response[0], dict):
|
||||
text = content_response[0].get('text', content_response[0].get('content', str(content_response[0])))
|
||||
full_response = str(text)
|
||||
else:
|
||||
full_response = str(content_response[0])
|
||||
else:
|
||||
full_response = ""
|
||||
elif isinstance(content_response, dict):
|
||||
full_response = str(content_response.get('text', content_response.get('content', str(content_response))))
|
||||
else:
|
||||
full_response = str(content_response) if content_response is not None else ""
|
||||
|
||||
# 4. 解析JSON响应
|
||||
try:
|
||||
# 尝试从响应中提取JSON
|
||||
# 移除可能的markdown代码块标记
|
||||
json_str = full_response.strip()
|
||||
if json_str.startswith("```json"):
|
||||
json_str = json_str[7:]
|
||||
if json_str.startswith("```"):
|
||||
json_str = json_str[3:]
|
||||
if json_str.endswith("```"):
|
||||
json_str = json_str[:-3]
|
||||
json_str = json_str.strip()
|
||||
|
||||
result_data = json.loads(json_str)
|
||||
title = result_data.get("title", "未知标题")
|
||||
episodic_type_raw = result_data.get("type", DEFAULT_TYPE)
|
||||
|
||||
# 5. 校验和归一化类型
|
||||
# 将类型转换为小写并去除空格
|
||||
episodic_type_normalized = str(episodic_type_raw).lower().strip()
|
||||
|
||||
# 检查是否在有效类型集合中
|
||||
if episodic_type_normalized in VALID_TYPES:
|
||||
episodic_type = episodic_type_normalized
|
||||
else:
|
||||
# 尝试映射常见的中文类型到英文
|
||||
type_mapping = {
|
||||
"对话": "conversation",
|
||||
"项目": "project_work",
|
||||
"工作": "project_work",
|
||||
"项目/工作": "project_work",
|
||||
"学习": "learning",
|
||||
"决策": "decision",
|
||||
"重要事件": "important_event",
|
||||
"事件": "important_event"
|
||||
}
|
||||
episodic_type = type_mapping.get(episodic_type_raw, DEFAULT_TYPE)
|
||||
logger.warning(
|
||||
f"LLM返回的类型 '{episodic_type_raw}' 不在有效集合中,"
|
||||
f"已归一化为 '{episodic_type}'"
|
||||
)
|
||||
|
||||
logger.info(f"成功生成标题和类型: title={title}, type={episodic_type}")
|
||||
return (title, episodic_type)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f"无法解析LLM响应为JSON: {full_response}")
|
||||
return ("解析失败", DEFAULT_TYPE)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"生成标题和类型时出错: {str(e)}", exc_info=True)
|
||||
return ("错误", DEFAULT_TYPE)
|
||||
|
||||
async def _process_chunk_summary(
|
||||
dialog: DialogData,
|
||||
chunk,
|
||||
@@ -63,10 +176,9 @@ async def _process_chunk_summary(
|
||||
title = None
|
||||
episodic_type = None
|
||||
try:
|
||||
from app.services.user_memory_service import UserMemoryService
|
||||
title, episodic_type = await UserMemoryService.generate_title_and_type_for_summary(
|
||||
title, episodic_type = await generate_title_and_type_for_summary(
|
||||
content=summary_text,
|
||||
end_user_id=dialog.group_id
|
||||
llm_client=llm_client
|
||||
)
|
||||
logger.info(f"Generated title and type for MemorySummary: title={title}, type={episodic_type}")
|
||||
except Exception as e:
|
||||
|
||||
@@ -260,17 +260,32 @@ class ForgettingStrategy:
|
||||
)
|
||||
|
||||
# 生成标题和类型(使用LLM)
|
||||
from app.services.user_memory_service import UserMemoryService
|
||||
from app.core.memory.storage_services.extraction_engine.knowledge_extraction.memory_summary import generate_title_and_type_for_summary
|
||||
|
||||
# 获取 LLM 客户端
|
||||
llm_client = None
|
||||
if config_id is not None and db is not None:
|
||||
try:
|
||||
llm_client = await self._get_llm_client(db, config_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"获取 LLM 客户端失败: {str(e)}")
|
||||
|
||||
# 生成标题和类型
|
||||
try:
|
||||
title, episodic_type = await UserMemoryService.generate_title_and_type_for_summary(
|
||||
content=summary_text,
|
||||
end_user_id=group_id
|
||||
)
|
||||
logger.info(f"成功为MemorySummary生成标题和类型: title={title}, type={episodic_type}")
|
||||
if llm_client is not None:
|
||||
title, episodic_type = await generate_title_and_type_for_summary(
|
||||
content=summary_text,
|
||||
llm_client=llm_client
|
||||
)
|
||||
logger.info(f"成功为MemorySummary生成标题和类型: title={title}, type={episodic_type}")
|
||||
else:
|
||||
logger.warning("LLM 客户端不可用,使用默认标题和类型")
|
||||
title = "未命名"
|
||||
episodic_type = "conversation"
|
||||
except Exception as e:
|
||||
logger.error(f"生成标题和类型失败,使用默认值: {str(e)}")
|
||||
title = "未命名"
|
||||
episodic_type = "其他"
|
||||
episodic_type = "conversation"
|
||||
|
||||
# 计算继承的激活值和重要性(取较高值)
|
||||
inherited_activation = max(statement_activation, entity_activation)
|
||||
|
||||
@@ -110,7 +110,7 @@ class BaiduSearchTool(BuiltinTool):
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
return ToolResult.success_result(
|
||||
data=result,
|
||||
data=result["results"],
|
||||
execution_time=execution_time
|
||||
)
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class DateTimeTool(BuiltinTool):
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
return ToolResult.success_result(
|
||||
data=result,
|
||||
data=result["result_data"],
|
||||
execution_time=execution_time
|
||||
)
|
||||
|
||||
@@ -123,12 +123,14 @@ class DateTimeTool(BuiltinTool):
|
||||
utc_now = datetime.now(timezone.utc)
|
||||
|
||||
return {
|
||||
"datetime": now.strftime(output_format),
|
||||
"timestamp": int(now.timestamp()),
|
||||
"timezone": timezone_str,
|
||||
"iso_format": now.isoformat(),
|
||||
"timestamp_ms": int(now.timestamp() * 1000),
|
||||
"utc_datetime": utc_now.strftime(output_format)
|
||||
"result_data": {
|
||||
"datetime": now.strftime(output_format),
|
||||
"timestamp": int(now.timestamp()),
|
||||
"timestamp_ms": int(now.timestamp() * 1000),
|
||||
"utc_datetime": utc_now.strftime(output_format),
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -148,7 +150,8 @@ class DateTimeTool(BuiltinTool):
|
||||
"original": input_value,
|
||||
"formatted": dt.strftime(output_format),
|
||||
"timestamp": int(dt.timestamp()),
|
||||
"iso_format": dt.isoformat()
|
||||
"iso_format": dt.isoformat(),
|
||||
"result_data": dt.strftime(output_format)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -189,7 +192,8 @@ class DateTimeTool(BuiltinTool):
|
||||
"original_timezone": from_timezone,
|
||||
"converted": converted_dt.strftime(output_format),
|
||||
"converted_timezone": to_timezone,
|
||||
"timestamp": int(converted_dt.timestamp())
|
||||
"timestamp": int(converted_dt.timestamp()),
|
||||
"result_data": converted_dt.strftime(output_format)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -219,7 +223,8 @@ class DateTimeTool(BuiltinTool):
|
||||
"timestamp": timestamp,
|
||||
"datetime": dt.strftime(output_format),
|
||||
"timezone": timezone_str,
|
||||
"iso_format": dt.isoformat()
|
||||
"iso_format": dt.isoformat(),
|
||||
"result_data": dt.strftime(output_format)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -249,7 +254,8 @@ class DateTimeTool(BuiltinTool):
|
||||
"datetime": input_value,
|
||||
"timezone": timezone_str,
|
||||
"timestamp": int(dt.timestamp()),
|
||||
"iso_format": dt.isoformat()
|
||||
"iso_format": dt.isoformat(),
|
||||
"result_data": int(dt.timestamp())
|
||||
}
|
||||
|
||||
def _calculate_datetime(self, kwargs) -> dict:
|
||||
@@ -287,7 +293,8 @@ class DateTimeTool(BuiltinTool):
|
||||
"calculation": calculation,
|
||||
"result": calculated_dt.strftime(output_format),
|
||||
"timezone": timezone_str,
|
||||
"timestamp": int(calculated_dt.timestamp())
|
||||
"timestamp": int(calculated_dt.timestamp()),
|
||||
"result_data": calculated_dt.strftime(output_format)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -69,7 +69,7 @@ class JsonTool(BuiltinTool):
|
||||
ToolParameter(
|
||||
name="json_path",
|
||||
type=ParameterType.STRING,
|
||||
description="JSON路径表达式(用于extract、insert、replace、delete、parse操作,如:$.user.name或users[0].name)",
|
||||
description="JSON路径表达式(用于insert、replace、delete、parse操作,如:$.user.name或users[0].name)",
|
||||
required=False
|
||||
),
|
||||
ToolParameter(
|
||||
@@ -136,7 +136,7 @@ class JsonTool(BuiltinTool):
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
return ToolResult.success_result(
|
||||
data=result,
|
||||
data=result["result_data"],
|
||||
execution_time=execution_time
|
||||
)
|
||||
|
||||
@@ -671,7 +671,8 @@ class JsonTool(BuiltinTool):
|
||||
"success": True,
|
||||
"value": current,
|
||||
"value_type": type(current).__name__,
|
||||
"value_json": json.dumps(current, indent=2, ensure_ascii=False) if isinstance(current, (dict, list)) else str(current)
|
||||
"value_json": json.dumps(current, indent=2, ensure_ascii=False) if isinstance(current, (dict, list)) else str(current),
|
||||
"result_data": json.dumps(current, indent=2, ensure_ascii=False) if isinstance(current, (dict, list)) else str(current)
|
||||
}
|
||||
|
||||
except (KeyError, IndexError, TypeError) as e:
|
||||
@@ -680,7 +681,8 @@ class JsonTool(BuiltinTool):
|
||||
"json_path": json_path,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"value": None
|
||||
"value": None,
|
||||
"result_data": None
|
||||
}
|
||||
|
||||
def _analyze_json_structure(self, data: Any, depth: int = 0) -> Dict[str, Any]:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from app.core.workflow.nodes.base_node import BaseNode, WorkflowState
|
||||
@@ -25,10 +27,10 @@ class ToolNode(BaseNode):
|
||||
# 获取租户ID和用户ID
|
||||
tenant_id = self.get_variable("sys.tenant_id", state)
|
||||
user_id = self.get_variable("sys.user_id", state)
|
||||
workspace_id = self.get_variable("sys.workspace_id", state)
|
||||
|
||||
# 如果没有租户ID,尝试从工作流ID获取
|
||||
if not tenant_id:
|
||||
workspace_id = self.get_variable("sys.workspace_id", state)
|
||||
if workspace_id:
|
||||
from app.repositories.tool_repository import ToolRepository
|
||||
with get_db_read() as db:
|
||||
@@ -63,21 +65,21 @@ class ToolNode(BaseNode):
|
||||
tool_id=self.typed_config.tool_id,
|
||||
parameters=rendered_parameters,
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id
|
||||
user_id=uuid.UUID(user_id),
|
||||
workspace_id=uuid.UUID(workspace_id)
|
||||
)
|
||||
|
||||
if result.success:
|
||||
logger.info(f"节点 {self.node_id} 工具执行成功")
|
||||
return {
|
||||
"success": True,
|
||||
"data": result.data,
|
||||
"data": result.data if isinstance(result.data, str) else json.dumps(result.data, ensure_ascii=False),
|
||||
"error_code": "",
|
||||
"execution_time": result.execution_time
|
||||
}
|
||||
else:
|
||||
logger.error(f"节点 {self.node_id} 工具执行失败: {result.error}")
|
||||
return {
|
||||
"success": False,
|
||||
"data": result.error,
|
||||
"data": result.error if isinstance(result.error, str) else json.dumps(result.error, ensure_ascii=False),
|
||||
"error_code": result.error_code,
|
||||
"execution_time": result.execution_time
|
||||
}
|
||||
Reference in New Issue
Block a user