feat(agentscope): add memory system and automation job support

- Add consumer_registry and pipeline_registry for runtime orchestration
- Add Visibility schema for message filtering
- Add PipelineSpec for agent pipeline configuration
- Add automation job models and configuration
- Remove memory_prompt.py (consolidated into memory system)
- Update runtime components: context_loader, context_service, orchestrator, runner, tasks
- Update toolkit: tool_config, tool_middleware, custom tools (calendar, user_lookup)
- Add auth_helpers and calendar_domain utilities
- Add system_agents.yaml configuration
This commit is contained in:
qzl
2026-03-19 18:42:35 +08:00
parent 0661016827
commit 0abf51e837
55 changed files with 2172 additions and 1233 deletions
@@ -10,8 +10,6 @@ from ag_ui.core import (
RunErrorEvent,
StepStartedEvent,
StepFinishedEvent,
TextMessageEndEvent,
ToolCallResultEvent,
)
from core.agentscope.runtime.ui_compiler import compile as compile_ui_hints
from schemas.agent.ui_hints import UiHintsPayload
@@ -94,9 +92,20 @@ def _build_run_finished(event: dict[str, Any]) -> RunFinishedEvent:
def _build_run_error(event: dict[str, Any]) -> RunErrorEvent:
data = event.get("data", {})
top_level_message = event.get("message")
message = top_level_message if isinstance(top_level_message, str) else ""
top_level_code = event.get("code")
code = top_level_code if isinstance(top_level_code, str) else None
if (not message or code is None) and isinstance(data, dict):
data_message = data.get("message")
if not message and isinstance(data_message, str):
message = data_message
data_code = data.get("code")
if code is None and isinstance(data_code, str):
code = data_code
return RunErrorEvent(
message=data.get("message", "Unknown error"),
code=data.get("code"),
message=message or "Unknown error",
code=code,
)
@@ -120,34 +129,12 @@ def _build_step_finished(event: dict[str, Any]) -> StepFinishedEvent:
)
def _build_text_end(event: dict[str, Any]) -> TextMessageEndEvent:
data = event.get("data", {})
return TextMessageEndEvent(
message_id=data.get("messageId", ""),
)
def _build_tool_result(event: dict[str, Any]) -> ToolCallResultEvent:
data = event.get("data", {})
content = data.get("result")
if not isinstance(content, str):
content = ""
return ToolCallResultEvent(
message_id=data.get("messageId", ""),
tool_call_id=data.get("toolCallId", ""),
content=content,
role="tool",
)
_BUILDER_MAP: dict[str, Any] = {
"run.started": _build_run_started,
"run.finished": _build_run_finished,
"run.error": _build_run_error,
"step.start": _build_step_started,
"step.finish": _build_step_finished,
"text.end": _build_text_end,
"tool.result": _build_tool_result,
}
@@ -208,6 +195,8 @@ def to_agui_wire_event(event: dict[str, Any] | BaseEvent) -> dict[str, Any]:
payload["runId"] = run_id
if isinstance(data, dict):
reserved = {"type", "threadId", "runId"}
if internal_type == "run.error":
reserved = {*reserved, "message", "code"}
payload.update({k: v for k, v in data.items() if k not in reserved})
return payload
@@ -29,6 +29,7 @@ class MessageRepository:
output_tokens: int = 0,
cost: Decimal = Decimal("0"),
latency_ms: int | None = None,
visibility_mask: int = 0,
) -> AgentChatMessage:
message = AgentChatMessage(
session_id=session_id,
@@ -42,6 +43,7 @@ class MessageRepository:
output_tokens=output_tokens,
cost=cost,
latency_ms=latency_ms,
visibility_mask=max(int(visibility_mask), 0),
)
self._session.add(message)
await self._session.flush()
+57 -1
View File
@@ -8,9 +8,12 @@ from core.agentscope.events.persistence import MessageRepository, SessionReposit
from core.logging import get_logger
from models.agent_chat_message import AgentChatMessageRole
from models.agent_chat_session import AgentChatSessionStatus
from models.system_agents import SystemAgents
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
from schemas.agent.runtime_models import AgentOutput, ToolAgentOutput
from schemas.agent.system_agent import AgentType
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
from schemas.messages.chat_message import AgentChatMessageMetadata
from sqlalchemy import select
class EventStore(Protocol):
@@ -45,6 +48,9 @@ class SqlAlchemyEventStore:
async with self._session_factory() as session:
session_repo = SessionRepository(session)
message_repo = MessageRepository(session)
stage_visibility_bit_map = await self._load_stage_visibility_bit_map(
session=session
)
chat_session = await session_repo.get_session(session_id=session_id)
if chat_session is None:
return
@@ -77,6 +83,7 @@ class SqlAlchemyEventStore:
chat_session=chat_session,
session_repo=session_repo,
message_repo=message_repo,
stage_visibility_bit_map=stage_visibility_bit_map,
)
elif event_type == "TOOL_CALL_RESULT":
await self._persist_tool_call_result(
@@ -85,6 +92,7 @@ class SqlAlchemyEventStore:
chat_session=chat_session,
session_repo=session_repo,
message_repo=message_repo,
stage_visibility_bit_map=stage_visibility_bit_map,
)
await session.commit()
@@ -97,6 +105,7 @@ class SqlAlchemyEventStore:
chat_session: Any,
session_repo: SessionRepository,
message_repo: MessageRepository,
stage_visibility_bit_map: dict[str, int],
) -> None:
message_id_raw = self._event_value(event, "messageId")
message_id = message_id_raw if isinstance(message_id_raw, str) else ""
@@ -188,6 +197,10 @@ class SqlAlchemyEventStore:
output_tokens=output_tokens,
cost=cost,
latency_ms=latency_ms,
visibility_mask=self._resolve_stage_visibility_mask(
event=event,
stage_visibility_bit_map=stage_visibility_bit_map,
),
)
current_status = getattr(chat_session, "status", AgentChatSessionStatus.RUNNING)
@@ -213,6 +226,7 @@ class SqlAlchemyEventStore:
chat_session: Any,
session_repo: SessionRepository,
message_repo: MessageRepository,
stage_visibility_bit_map: dict[str, int],
) -> None:
run_id = self._event_value(event, "runId")
run_id_value = run_id if isinstance(run_id, str) and run_id else None
@@ -256,6 +270,10 @@ class SqlAlchemyEventStore:
content=content,
tool_name=tool_output.tool_name,
metadata=metadata_model.model_dump(mode="json", exclude_none=True),
visibility_mask=self._resolve_stage_visibility_mask(
event=event,
stage_visibility_bit_map=stage_visibility_bit_map,
),
)
current_status = getattr(chat_session, "status", AgentChatSessionStatus.RUNNING)
@@ -279,6 +297,44 @@ class SqlAlchemyEventStore:
return AgentChatMessageRole.TOOL
return AgentChatMessageRole.ASSISTANT
def _resolve_stage_visibility_mask(
self,
*,
event: dict[str, Any],
stage_visibility_bit_map: dict[str, int],
) -> int:
base = bit_mask(bit=int(SystemVisibilityBit.UI_HISTORY))
raw_stage = self._event_value(event, "stage")
if not isinstance(raw_stage, str):
return base
normalized_stage = raw_stage.strip().lower()
bit = stage_visibility_bit_map.get(normalized_stage)
if bit is None and normalized_stage == AgentType.MEMORY.value:
bit = 18
if bit is None:
return base
return base | bit_mask(bit=bit)
async def _load_stage_visibility_bit_map(
self,
*,
session: Any,
) -> dict[str, int]:
stmt = select(SystemAgents.agent_type, SystemAgents.config).where(
SystemAgents.agent_type.in_(
[AgentType.ROUTER.value, AgentType.WORKER.value, AgentType.MEMORY.value]
)
)
rows = (await session.execute(stmt)).all()
bit_map: dict[str, int] = {}
for agent_type, raw_config in rows:
if not isinstance(agent_type, str):
continue
config_payload = raw_config if isinstance(raw_config, dict) else {}
llm_config = SystemAgentLLMConfig.model_validate(config_payload)
bit_map[agent_type.strip().lower()] = llm_config.visibility_consumer_bit
return bit_map
async def _update_session_state(
self,
*,