mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2026-10-13 16:10:05 +08:00
✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 (#2146)
* ✨ feat!(llm): 重构并升级大语言模型服务为全新 AI 智能体框架 - 【重构】将原 services/llm 重构并迁移至全新的 services/ai 架构,提供向下兼容垫片 - 【新增】引入 Agent、Team、Workflow 三大智能体与工作流编排范式 - 【新增】引入基于 RAG 的长期向量记忆与中期槽位记忆系统 - 【新增】引入基于 Docker 的安全代码执行沙箱环境 - 【新增】支持 MCP 协议,允许动态管理和调用 MCP 服务 - 【新增】引入输入输出安全合规护栏与自愈反思机制 - 【优化】重构并优化多厂商 API 适配器 (Gemini, OpenAI, DeepSeek, GLM 等) - 【优化】优化日志脱敏与 Token 预估机制 - 【移除】移除旧版 llm default 和 llm reset-key 命令,新增 llm mcp 管理命令 * 🔧 chore(deps): 更新项目依赖与配置 - 添加 mcp、jieba 和 aiodocker 依赖到配置文件及 requirements.txt - 在 pyright 配置中设置 reportMissingImports 为 none - 调整 .gitignore 中 resources 目录的忽略规则 * ♻️ refactor(tools): 重构工具终止机制并清理知识库日志输出 - 统一使用 `context.state["__end_run__"]` 替代 `EndRunResult` 控制任务结束 - 移除文件系统和向量知识库检索工具中 `ToolResult` 的 `.with_log` 调用 - 调整指令处理器(Directive)的返回值为 `tool_res.output` - 修复部分类型检查警告并优化联合类型判断语法 * ♻️ refactor(tools): 重构工具副作用指令与控制流熔断机制 - 引入 `DirectivePayload` 及 `ToolResult` 的子类以结构化表达工具副作用 - 移除通过 `context.state` 传递魔术变量的隐式控制流设计 - 重构 `DirectiveManager` 处理器接口,直接在处理器中修改 `AgentState` 并构建 `AgentRunResult` - 在 `StandardAgentExecutor` 中统一通过 `directive_manager` 调度工具返回的副作用指令 - 补全 `MessageBuilder` 中部分核心方法的文档注释 * 🐛 fix(sandbox): 修复 Docker 沙箱容器状态检测与会话清理逻辑 -【修复】修正 `is_alive` 中直接读取私有属性的问题,改用 `show()` 返回值 -【修复】解决 `execute_code` 中缓存的执行器与当前会话不一致的问题 -【优化】在清理工作区前增加容器存活检测,避免向已死容器发送请求 -【优化】创建容器时增加运行状态校验,若已停止则自动从缓存中移除并重建 -【优化】优化容器销毁和清理逻辑,静默处理容器不存在 (404) 的异常 * 📝 docs(core): 补充核心模块初始化方法的文档注释 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
webjoin111
pre-commit-ci[bot]
parent
bdc1374848
commit
80fc5b86a7
@@ -0,0 +1,21 @@
|
||||
from .backends import (
|
||||
AbstractMemoryRecord,
|
||||
AbstractSlotRecord,
|
||||
InMemoryChatContext,
|
||||
MemoryScope,
|
||||
TortoiseChatContext,
|
||||
TortoiseSlotContext,
|
||||
get_orm_chat_context,
|
||||
get_orm_slot_context,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AbstractMemoryRecord",
|
||||
"AbstractSlotRecord",
|
||||
"InMemoryChatContext",
|
||||
"MemoryScope",
|
||||
"TortoiseChatContext",
|
||||
"TortoiseSlotContext",
|
||||
"get_orm_chat_context",
|
||||
"get_orm_slot_context",
|
||||
]
|
||||
@@ -0,0 +1,465 @@
|
||||
import asyncio
|
||||
import base64
|
||||
from collections.abc import Callable
|
||||
import datetime
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from zhenxun.services.ai.context.rag.engine import ScopedRAGClient
|
||||
|
||||
from nonebot.utils import is_coroutine_callable
|
||||
from tortoise import fields
|
||||
from tortoise.timezone import now
|
||||
|
||||
from zhenxun.services.ai.context.memory.storage.interfaces import (
|
||||
BaseChatContext,
|
||||
BaseSlotContext,
|
||||
)
|
||||
from zhenxun.services.ai.context.memory.types import (
|
||||
MemorySlot,
|
||||
SessionMetadata,
|
||||
)
|
||||
from zhenxun.services.ai.context.rag.models import BaseRecord, SearchResult
|
||||
from zhenxun.services.ai.core.messages import (
|
||||
AssistantMessage,
|
||||
LLMContentPart,
|
||||
LLMMessage,
|
||||
SystemMessage,
|
||||
ToolMessage,
|
||||
UserMessage,
|
||||
)
|
||||
from zhenxun.services.ai.utils.scope import ScopeSelector
|
||||
from zhenxun.services.db_context import Model
|
||||
from zhenxun.utils.pydantic_compat import TypeAdapter, model_dump
|
||||
|
||||
|
||||
class DBMessageSerializer:
|
||||
"""将 LLMMessage 与数据库 JSON 格式进行序列化/反序列化的帮助类"""
|
||||
|
||||
@staticmethod
|
||||
def deserialize_content(content_raw: Any) -> list[LLMContentPart]:
|
||||
from zhenxun.services.ai.core.messages import TextPart
|
||||
|
||||
content_parts: list[LLMContentPart] = []
|
||||
if isinstance(content_raw, list):
|
||||
adapter = TypeAdapter(LLMContentPart)
|
||||
for p in content_raw:
|
||||
if isinstance(p, dict):
|
||||
for k in list(p.keys()):
|
||||
if k.startswith("_is_b64_"):
|
||||
orig_k = k[8:]
|
||||
if orig_k in p and isinstance(p[orig_k], str):
|
||||
p[orig_k] = base64.b64decode(p[orig_k])
|
||||
p.pop(k, None)
|
||||
content_parts.append(adapter.validate_python(p))
|
||||
elif isinstance(content_raw, str):
|
||||
content_parts.append(TextPart(text=content_raw))
|
||||
return content_parts
|
||||
|
||||
@staticmethod
|
||||
def serialize_content(content_payload: Any) -> list[dict[str, Any]]:
|
||||
from pathlib import Path
|
||||
|
||||
if isinstance(content_payload, str):
|
||||
return [{"type": "text", "text": content_payload}]
|
||||
elif isinstance(content_payload, list):
|
||||
processed_content = []
|
||||
for p in content_payload:
|
||||
p_dump = (
|
||||
model_dump(p, exclude_none=True)
|
||||
if hasattr(p, "model_dump")
|
||||
else (p.copy() if isinstance(p, dict) else p)
|
||||
)
|
||||
if isinstance(p_dump, dict):
|
||||
for k, v in list(p_dump.items()):
|
||||
if isinstance(v, bytes):
|
||||
p_dump[k] = base64.b64encode(v).decode("utf-8")
|
||||
p_dump[f"_is_b64_{k}"] = True
|
||||
elif isinstance(v, Path):
|
||||
p_dump[k] = str(v)
|
||||
processed_content.append(p_dump)
|
||||
return (
|
||||
processed_content
|
||||
if processed_content
|
||||
else [
|
||||
{"type": "text", "text": "[仅包含思维链或工具调度,无实质文本输出]"}
|
||||
]
|
||||
)
|
||||
return []
|
||||
|
||||
|
||||
class MemoryScope:
|
||||
"""长期记忆的作用域视图与 RAG 管线。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rag_client: "ScopedRAGClient",
|
||||
):
|
||||
self.rag_client = rag_client
|
||||
self._background_tasks: set[Any] = set()
|
||||
|
||||
async def remember(
|
||||
self,
|
||||
session: SessionMetadata,
|
||||
content: str,
|
||||
importance: float = 0.5,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""通过 RAG Ingestion Pipeline 完成记忆落盘"""
|
||||
meta = metadata.copy() if metadata else {}
|
||||
meta.update(
|
||||
{
|
||||
"scope": session.scope_prefix,
|
||||
"importance": importance,
|
||||
"created_at": time.time(),
|
||||
}
|
||||
)
|
||||
record = BaseRecord(content=content, metadata=meta)
|
||||
|
||||
await self.rag_client.ingest([record])
|
||||
|
||||
async def recall(
|
||||
self,
|
||||
session: SessionMetadata,
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
metadata_filter: dict[str, Any] | None = None,
|
||||
) -> list[SearchResult]:
|
||||
"""委托至 Retriever 检索与重排,并触发读时惰性强化"""
|
||||
matches = await self.rag_client.search(
|
||||
query=query,
|
||||
limit=limit,
|
||||
scopes=session.accessible_scopes,
|
||||
metadata_filters=metadata_filter,
|
||||
)
|
||||
if matches:
|
||||
task = asyncio.create_task(
|
||||
self._reinforce_memories([m.record for m in matches])
|
||||
)
|
||||
self._background_tasks.add(task)
|
||||
task.add_done_callback(self._background_tasks.discard)
|
||||
return matches
|
||||
|
||||
async def update(
|
||||
self,
|
||||
session: SessionMetadata,
|
||||
record_id: str,
|
||||
new_content: str,
|
||||
importance: float = 0.5,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
"""原子更新:通过先删后插,确保底层向量(Embedding)能根据新文本被正确刷新"""
|
||||
deleted_count = await self.forget(session, record_ids=[record_id])
|
||||
if deleted_count > 0:
|
||||
await self.remember(
|
||||
session=session,
|
||||
content=new_content,
|
||||
importance=importance,
|
||||
metadata=metadata,
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
async def forget(
|
||||
self, session: SessionMetadata, record_ids: list[str] | None = None
|
||||
) -> int:
|
||||
return await self.rag_client.delete(
|
||||
record_ids=record_ids,
|
||||
)
|
||||
|
||||
async def _reinforce_memories(self, records: list[BaseRecord]):
|
||||
import time
|
||||
|
||||
now = time.time()
|
||||
for r in records:
|
||||
r.metadata["access_count"] = r.metadata.get("access_count", 0) + 1
|
||||
r.metadata["last_accessed_at"] = now
|
||||
await self.rag_client.storage.update(r)
|
||||
|
||||
|
||||
class InMemoryChatContext(BaseChatContext):
|
||||
def __init__(self):
|
||||
self._messages: dict[str, list[LLMMessage]] = {}
|
||||
|
||||
async def get_messages(self, session: SessionMetadata) -> list[LLMMessage]:
|
||||
return list(self._messages.get(session.session_id, []))
|
||||
|
||||
async def search(
|
||||
self, query: str, session: SessionMetadata, limit: int = 10
|
||||
) -> list[LLMMessage]:
|
||||
results = []
|
||||
for msg in self._messages.get(session.session_id, []):
|
||||
if query in msg.extract_text:
|
||||
results.append(msg)
|
||||
if len(results) >= limit:
|
||||
break
|
||||
return results
|
||||
|
||||
async def add_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
if session.session_id not in self._messages:
|
||||
self._messages[session.session_id] = []
|
||||
self._messages[session.session_id].extend(messages)
|
||||
|
||||
async def set_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
self._messages[session.session_id] = list(messages)
|
||||
|
||||
async def clear(self, session: SessionMetadata) -> None:
|
||||
self._messages.pop(session.session_id, None)
|
||||
|
||||
async def clear_by_query(self, query: ScopeSelector) -> None:
|
||||
"""内存级:前缀匹配清理所有符合要求的短期会话"""
|
||||
scope_prefix = query.scope_prefix
|
||||
keys_to_delete = [
|
||||
sid for sid in self._messages.keys() if sid.startswith(scope_prefix)
|
||||
]
|
||||
for sid in keys_to_delete:
|
||||
self._messages.pop(sid, None)
|
||||
|
||||
|
||||
class AbstractMemoryRecord(Model):
|
||||
"""Tortoise ORM 短期记忆持久化基类 (Mixin)。"""
|
||||
|
||||
id = fields.UUIDField(pk=True, description="主键")
|
||||
session_id = fields.CharField(max_length=255, index=True)
|
||||
role = fields.CharField(max_length=32)
|
||||
content = fields.JSONField()
|
||||
api_context = fields.JSONField(null=True)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
metadata = fields.JSONField(null=True)
|
||||
|
||||
class Meta: # type: ignore
|
||||
abstract = True
|
||||
|
||||
|
||||
class TortoiseChatContext(BaseChatContext):
|
||||
def __init__(
|
||||
self,
|
||||
model_class: type[AbstractMemoryRecord],
|
||||
custom_save_hook: Callable[
|
||||
[AbstractMemoryRecord, LLMMessage, SessionMetadata], Any
|
||||
]
|
||||
| None = None,
|
||||
):
|
||||
self.model_class = model_class
|
||||
self.custom_save_hook = custom_save_hook
|
||||
|
||||
def _row_to_message(self, row: AbstractMemoryRecord) -> LLMMessage:
|
||||
content_parts = DBMessageSerializer.deserialize_content(row.content)
|
||||
metadata: dict[str, Any] | None = (
|
||||
row.metadata if isinstance(row.metadata, dict) else None
|
||||
)
|
||||
kwargs = {
|
||||
"content": content_parts,
|
||||
"metadata": metadata,
|
||||
"created_at": row.created_at.timestamp() if row.created_at else time.time(),
|
||||
}
|
||||
role = row.role
|
||||
if role == "system":
|
||||
return cast(LLMMessage, SystemMessage(**kwargs))
|
||||
elif role == "user":
|
||||
return cast(LLMMessage, UserMessage(**kwargs))
|
||||
elif role == "assistant":
|
||||
return cast(LLMMessage, AssistantMessage(**kwargs))
|
||||
elif role == "tool":
|
||||
return cast(LLMMessage, ToolMessage(**kwargs))
|
||||
return cast(LLMMessage, LLMMessage(role=role, **kwargs))
|
||||
|
||||
async def get_messages(self, session: SessionMetadata) -> list[LLMMessage]:
|
||||
rows = (
|
||||
await self.model_class.filter(session_id=session.session_id)
|
||||
.order_by("created_at")
|
||||
.all()
|
||||
)
|
||||
return [self._row_to_message(row) for row in rows]
|
||||
|
||||
async def search(
|
||||
self, query: str, session: SessionMetadata, limit: int = 10
|
||||
) -> list[LLMMessage]:
|
||||
rows = (
|
||||
await self.model_class.filter(
|
||||
session_id=session.session_id, content__icontains=query
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
return [self._row_to_message(row) for row in reversed(rows)]
|
||||
|
||||
async def add_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
if not messages:
|
||||
return
|
||||
|
||||
base_time = now()
|
||||
|
||||
last_msg = (
|
||||
await self.model_class.filter(session_id=session.session_id)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if last_msg and last_msg.created_at and last_msg.created_at >= base_time:
|
||||
base_time = last_msg.created_at + datetime.timedelta(milliseconds=10)
|
||||
|
||||
orm_objects = []
|
||||
for i, msg in enumerate(messages):
|
||||
content_payload = DBMessageSerializer.serialize_content(msg.content)
|
||||
|
||||
msg_time = base_time + datetime.timedelta(milliseconds=i * 10)
|
||||
orm_obj = self.model_class(
|
||||
session_id=session.session_id,
|
||||
role=msg.role,
|
||||
content=content_payload,
|
||||
api_context=None,
|
||||
metadata=msg.metadata,
|
||||
created_at=msg_time,
|
||||
)
|
||||
if self.custom_save_hook:
|
||||
if is_coroutine_callable(self.custom_save_hook):
|
||||
await self.custom_save_hook(orm_obj, msg, session)
|
||||
else:
|
||||
self.custom_save_hook(orm_obj, msg, session)
|
||||
orm_objects.append(orm_obj)
|
||||
if orm_objects:
|
||||
await self.model_class.bulk_create(orm_objects)
|
||||
|
||||
async def set_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
await self.clear(session)
|
||||
await self.add_messages(session, messages)
|
||||
|
||||
async def clear(self, session: SessionMetadata) -> None:
|
||||
await self.model_class.filter(session_id=session.session_id).delete()
|
||||
|
||||
async def clear_by_query(self, query: ScopeSelector) -> None:
|
||||
"""ORM 级:利用数据库 startswith 原生语法批量级联删除短期记忆"""
|
||||
scope_prefix = query.scope_prefix
|
||||
await self.model_class.filter(session_id__startswith=scope_prefix).delete()
|
||||
|
||||
|
||||
def get_orm_chat_context(
|
||||
model_class: type[AbstractMemoryRecord],
|
||||
custom_save_hook: Callable[[AbstractMemoryRecord, LLMMessage, SessionMetadata], Any]
|
||||
| None = None,
|
||||
) -> TortoiseChatContext:
|
||||
"""
|
||||
[工厂方法] 供第三方开发者调用,
|
||||
将 Tortoise ORM 表直接包装为对话历史记录系统。
|
||||
"""
|
||||
return TortoiseChatContext(
|
||||
model_class=model_class, custom_save_hook=custom_save_hook
|
||||
)
|
||||
|
||||
|
||||
class AbstractSlotRecord(Model):
|
||||
"""Tortoise ORM 记忆槽持久化基类 (Mixin)。"""
|
||||
|
||||
id = fields.CharField(
|
||||
pk=True, max_length=128, description="复合主键: session_id + label"
|
||||
)
|
||||
session_id = fields.CharField(max_length=255, index=True)
|
||||
label = fields.CharField(max_length=64, index=True)
|
||||
content = fields.TextField()
|
||||
size_limit = fields.IntField(default=2000)
|
||||
pinned = fields.BooleanField(default=True)
|
||||
scope = fields.CharField(max_length=255)
|
||||
description = fields.CharField(max_length=255, default="")
|
||||
created_at = fields.FloatField()
|
||||
updated_at = fields.FloatField()
|
||||
|
||||
class Meta: # type: ignore
|
||||
abstract = True
|
||||
|
||||
|
||||
class TortoiseSlotContext(BaseSlotContext):
|
||||
def __init__(self, model_class: type[AbstractSlotRecord]):
|
||||
self.model_class = model_class
|
||||
|
||||
def _row_to_slot(self, row: AbstractSlotRecord) -> MemorySlot:
|
||||
return MemorySlot(
|
||||
label=row.label,
|
||||
content=row.content,
|
||||
size_limit=row.size_limit,
|
||||
pinned=row.pinned,
|
||||
scope=row.scope,
|
||||
description=row.description,
|
||||
created_at=row.created_at,
|
||||
updated_at=row.updated_at,
|
||||
)
|
||||
|
||||
async def get_slot(self, session: SessionMetadata, label: str) -> MemorySlot | None:
|
||||
rows = await self.model_class.filter(
|
||||
session_id__in=session.accessible_scopes, label=label
|
||||
).all()
|
||||
|
||||
row_map = {r.session_id: r for r in rows}
|
||||
for scope in reversed(session.accessible_scopes):
|
||||
if scope in row_map:
|
||||
return self._row_to_slot(row_map[scope])
|
||||
return None
|
||||
|
||||
async def set_slot(self, session: SessionMetadata, slot: MemorySlot) -> None:
|
||||
composite_id = f"{slot.scope}_{slot.label}"
|
||||
|
||||
await self.model_class.update_or_create(
|
||||
id=composite_id,
|
||||
defaults={
|
||||
"session_id": slot.scope,
|
||||
"label": slot.label,
|
||||
"content": slot.content,
|
||||
"size_limit": slot.size_limit,
|
||||
"pinned": slot.pinned,
|
||||
"scope": slot.scope,
|
||||
"description": slot.description,
|
||||
"created_at": slot.created_at,
|
||||
"updated_at": slot.updated_at,
|
||||
},
|
||||
)
|
||||
|
||||
async def delete_slot(
|
||||
self, session: SessionMetadata, label: str, scope: str
|
||||
) -> None:
|
||||
composite_id = f"{scope}_{label}"
|
||||
await self.model_class.filter(id=composite_id).delete()
|
||||
|
||||
async def list_pinned_slots(self, session: SessionMetadata) -> list[MemorySlot]:
|
||||
rows = await self.model_class.filter(
|
||||
session_id__in=session.accessible_scopes, pinned=True
|
||||
).all()
|
||||
|
||||
merged = {}
|
||||
for scope in session.accessible_scopes:
|
||||
for row in rows:
|
||||
if row.session_id == scope:
|
||||
merged[row.label] = self._row_to_slot(row)
|
||||
|
||||
return [s for s in merged.values() if s.content.strip()]
|
||||
|
||||
async def list_all_slots(self, session: SessionMetadata) -> list[MemorySlot]:
|
||||
rows = await self.model_class.filter(
|
||||
session_id__in=session.accessible_scopes
|
||||
).all()
|
||||
|
||||
merged = {}
|
||||
for scope in session.accessible_scopes:
|
||||
for row in rows:
|
||||
if row.session_id == scope:
|
||||
merged[row.label] = self._row_to_slot(row)
|
||||
return list(merged.values())
|
||||
|
||||
async def clear_by_query(self, query: ScopeSelector) -> None:
|
||||
scope_prefix = query.scope_prefix
|
||||
await self.model_class.filter(session_id__startswith=scope_prefix).delete()
|
||||
|
||||
|
||||
def get_orm_slot_context(model_class: type[AbstractSlotRecord]) -> TortoiseSlotContext:
|
||||
"""
|
||||
[工厂方法] 供第三方开发者调用,将 Tortoise ORM 表直接包装为记忆槽存储系统。
|
||||
"""
|
||||
return TortoiseSlotContext(model_class=model_class)
|
||||
@@ -0,0 +1,110 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
|
||||
from zhenxun.services.ai.context.memory.types import (
|
||||
MemorySlot,
|
||||
SessionMetadata,
|
||||
)
|
||||
from zhenxun.services.ai.core.messages import AgentMessage, LLMMessage
|
||||
from zhenxun.services.ai.run.context import RunContext
|
||||
from zhenxun.services.ai.utils.scope import ScopeSelector
|
||||
|
||||
|
||||
class BaseChatContext(ABC):
|
||||
"""短期对话历史记忆接口"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_messages(self, session: SessionMetadata) -> list[LLMMessage]:
|
||||
"""获取当前会话的所有历史消息。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def search(
|
||||
self, query: str, session: SessionMetadata, limit: int = 10
|
||||
) -> list[LLMMessage]:
|
||||
"""根据查询词搜索当前会话的历史消息。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def add_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
"""向当前会话追加消息。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def set_messages(
|
||||
self, session: SessionMetadata, messages: list[LLMMessage]
|
||||
) -> None:
|
||||
"""重置并设置当前会话的消息列表。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def clear(self, session: SessionMetadata) -> None:
|
||||
"""清空当前会话的历史消息。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def clear_by_query(self, query: ScopeSelector) -> None:
|
||||
"""根据条件领域查询对象清理对话历史。"""
|
||||
...
|
||||
|
||||
|
||||
class BaseSlotContext(ABC):
|
||||
"""中期记忆槽持久化接口"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_slot(self, session: SessionMetadata, label: str) -> MemorySlot | None:
|
||||
"""获取指定会话下特定标签的记忆槽。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def set_slot(self, session: SessionMetadata, slot: MemorySlot) -> None:
|
||||
"""设置或更新指定会话下的记忆槽。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def delete_slot(
|
||||
self, session: SessionMetadata, label: str, scope: str
|
||||
) -> None:
|
||||
"""删除指定会话下特定标签和作用域的记忆槽。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def list_pinned_slots(self, session: SessionMetadata) -> list[MemorySlot]:
|
||||
"""列出当前会话所有固定的记忆槽。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def list_all_slots(self, session: SessionMetadata) -> list[MemorySlot]:
|
||||
"""列出当前会话的所有记忆槽(包括未置顶的)。"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def clear_by_query(self, query: ScopeSelector) -> None:
|
||||
"""根据条件领域查询对象清理记忆槽。"""
|
||||
...
|
||||
|
||||
|
||||
class BaseMemoryReducer(ABC):
|
||||
"""记忆压缩器基类"""
|
||||
|
||||
@abstractmethod
|
||||
async def reduce(
|
||||
self,
|
||||
messages: list[LLMMessage],
|
||||
current_tokens: int,
|
||||
model_name: str,
|
||||
base_overhead: int = 0,
|
||||
) -> tuple[list[LLMMessage], bool, int]:
|
||||
"""对消息列表进行压缩处理。"""
|
||||
...
|
||||
|
||||
|
||||
class BaseMemoryIngestionMiddleware(ABC):
|
||||
"""记忆入库中间件基类,在写入数据库前拦截并修改/清洗消息"""
|
||||
|
||||
@abstractmethod
|
||||
async def process(
|
||||
self, messages: Sequence[AgentMessage], context: RunContext
|
||||
) -> list[AgentMessage]: ...
|
||||
Reference in New Issue
Block a user