refactor: 简化 AgentScope 运行时模块与 prompt 系统

This commit is contained in:
zl-q
2026-03-15 17:14:15 +08:00
parent 61997f3613
commit 072c09d99d
32 changed files with 750 additions and 1863 deletions
+119 -143
View File
@@ -1,202 +1,178 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import base64
from typing import Any
from uuid import UUID
from ag_ui.core import RunAgentInput
from sqlalchemy import select
from agentscope.message import Msg
from core.agentscope.events import (
AgentScopeAgUiCodec,
AgentScopeEventPipeline,
RedisStreamBus,
SqlAlchemyEventStore,
)
from core.agentscope.schemas.agui_input import (
extract_latest_tool_result,
parse_run_input,
)
from core.agentscope.runtime.orchestrator import AgentScopeRuntimeOrchestrator
from core.agentscope.schemas.agui_input import parse_run_input
from core.agentscope.tools.tool_result_storage import create_tool_result_storage
from core.auth.models import CurrentUser
from core.config.settings import config
from core.db.session import AsyncSessionLocal
from core.logging import get_logger
from core.taskiq.app import bulk_broker, critical_broker, default_broker
from core.agentscope.tools.tool_result_storage import create_tool_result_storage
from models.agent_chat_message import AgentChatMessage, AgentChatMessageRole
from schemas.user import UserContext, parse_profile_settings
from schemas.user import UserContext
from services.base.redis import get_or_init_redis_client
from services.base.supabase import supabase_service
from v1.agent.dependencies import get_agent_service
from v1.users.dependencies import get_user_service
logger = get_logger("core.agentscope.runtime.tasks")
AgentScopeRuntimeOrchestrator: type[Any] | None = None
def _load_runtime() -> type[Any]:
return AgentScopeRuntimeOrchestrator
def _load_runtime_type() -> type[Any]:
global AgentScopeRuntimeOrchestrator
if AgentScopeRuntimeOrchestrator is None:
from core.agentscope.runtime.orchestrator import (
AgentScopeRuntimeOrchestrator as _ASRO,
)
AgentScopeRuntimeOrchestrator = _ASRO
runtime_type = AgentScopeRuntimeOrchestrator
if runtime_type is None:
raise RuntimeError("failed to load AgentScopeRuntimeOrchestrator")
return runtime_type
def _build_user_context(*, owner_id: UUID, run_input: RunAgentInput) -> UserContext:
forwarded = (
run_input.forwarded_props if isinstance(run_input.forwarded_props, dict) else {}
)
username = str(forwarded.get("username", "user")).strip() or "user"
bio_value = forwarded.get("bio")
bio = str(bio_value).strip() if isinstance(bio_value, str) else None
email_value = forwarded.get("email")
email = str(email_value).strip() if isinstance(email_value, str) else None
avatar_value = forwarded.get("avatarUrl")
avatar_url = str(avatar_value).strip() if isinstance(avatar_value, str) else None
profile_settings = forwarded.get("profileSettings")
settings_raw = profile_settings if isinstance(profile_settings, dict) else None
return UserContext(
id=str(owner_id),
username=username,
email=email,
avatar_url=avatar_url,
bio=bio,
settings=parse_profile_settings(settings_raw),
)
async def _build_user_context(
*,
owner_id: UUID,
session: Any,
) -> UserContext:
current_user = CurrentUser(id=owner_id)
user_service = get_user_service(session=session, user=current_user)
return await user_service.get_me()
async def _build_recent_context_messages(
*,
session: Any,
thread_id: str,
current_run_id: str,
max_messages: int = 20,
) -> list[dict[str, Any]]:
try:
session_uuid = UUID(thread_id)
except ValueError:
) -> list[Msg]:
agent_service = get_agent_service(session)
result = await agent_service.load_agent_input_messages(thread_id=thread_id)
if not result:
return []
utc_now = datetime.now(timezone.utc)
start_of_today = utc_now.replace(hour=0, minute=0, second=0, microsecond=0)
start_of_yesterday = start_of_today - timedelta(days=1)
raw_messages: list[dict[str, Any]] = result.get("messages") or []
if not raw_messages:
return []
stmt = (
select(AgentChatMessage)
.where(AgentChatMessage.session_id == session_uuid)
.where(AgentChatMessage.deleted_at.is_(None))
.where(AgentChatMessage.created_at >= start_of_yesterday)
.order_by(AgentChatMessage.seq.asc())
)
rows = (await session.execute(stmt)).scalars().all()
converted: list[Msg] = []
normalized: list[dict[str, Any]] = []
for row in rows:
metadata = row.metadata_json if isinstance(row.metadata_json, dict) else {}
if metadata.get("run_id") == current_run_id:
continue
role = (
row.role.value
if isinstance(row.role, AgentChatMessageRole)
else str(row.role)
)
if role not in {"user", "assistant"}:
continue
normalized.append(
{
"id": str(row.id),
"role": role,
"content": row.content,
}
for msg in raw_messages:
role = msg.get("role")
content = msg.get("content", "")
metadata = msg.get("metadata")
if role == "user" and metadata:
attachments = metadata.get("user_message_attachments")
if attachments:
bucket = attachments.get("bucket")
path = attachments.get("path")
mime_type = attachments.get("mime_type")
if bucket and path:
try:
image_bytes = await supabase_service.download_bytes(
bucket=bucket,
path=path,
)
b64_data = base64.b64encode(image_bytes).decode("utf-8")
converted.append(
Msg(
name="user",
role="user",
content=[
{"type": "text", "text": content},
{
"type": "image",
"source": {
"type": "base64",
"media_type": mime_type or "image/png",
"data": b64_data,
},
},
],
)
)
continue
except Exception:
pass
if role == "tool":
role = "assistant"
converted.append(
Msg(
name=role or "user",
role=role if role in ("user", "assistant", "system") else "user",
content=content,
)
)
if len(normalized) <= max_messages:
return normalized
return normalized[-max_messages:]
return converted
async def run_agentscope_task(command: dict[str, Any]) -> dict[str, object]:
command_type = str(command.get("command", "run")).strip().lower()
raw_run_input = command.get("run_input")
raw_owner_id = command.get("owner_id")
run_input_raw = command.get("run_input")
if not isinstance(raw_run_input, dict):
raise ValueError("run_input is required")
if not isinstance(raw_owner_id, str) or not raw_owner_id.strip():
raise ValueError("owner_id is required")
if run_input_raw is None:
raise ValueError("run_input is required")
run_input = parse_run_input(run_input_raw)
thread_id = run_input.thread_id
run_id = run_input.run_id
owner_id = UUID(raw_owner_id)
if command_type not in {"run", "resume"}:
if command_type != "run":
raise ValueError("invalid command type")
orchestrator_type = _load_runtime_type()
parsed_run_input = parse_run_input(raw_run_input)
if command_type == "resume":
extract_latest_tool_result(parsed_run_input)
user_context = _build_user_context(owner_id=owner_id, run_input=parsed_run_input)
redis_client = await get_or_init_redis_client()
bus = RedisStreamBus(
client=redis_client,
stream_prefix=config.agent_runtime.redis_stream_prefix,
read_count=config.agent_runtime.redis_stream_read_count,
block_ms=config.agent_runtime.redis_stream_block_ms,
)
pipeline = AgentScopeEventPipeline(
codec=AgentScopeAgUiCodec(),
store=SqlAlchemyEventStore(
session_factory=AsyncSessionLocal,
tool_result_storage=create_tool_result_storage(),
tool_result_bucket=config.storage.bucket,
),
bus=bus,
)
runtime = orchestrator_type(
pipeline=pipeline,
)
orchestrator = _load_runtime()
async with AsyncSessionLocal() as session:
user_context = await _build_user_context(owner_id=owner_id, session=session)
redis_client = await get_or_init_redis_client()
bus = RedisStreamBus(
client=redis_client,
stream_prefix=config.agent_runtime.redis_stream_prefix,
read_count=config.agent_runtime.redis_stream_read_count,
block_ms=config.agent_runtime.redis_stream_block_ms,
)
pipeline = AgentScopeEventPipeline(
codec=AgentScopeAgUiCodec(),
store=SqlAlchemyEventStore(
session_factory=AsyncSessionLocal,
tool_result_storage=create_tool_result_storage(),
tool_result_bucket=config.storage.bucket,
),
bus=bus,
)
runtime = orchestrator(
pipeline=pipeline,
)
context_messages = await _build_recent_context_messages(
session=session,
thread_id=parsed_run_input.thread_id,
current_run_id=parsed_run_input.run_id,
thread_id=thread_id,
)
if context_messages:
parsed_run_input = parsed_run_input.model_copy(
update={
"messages": [
*context_messages,
*parsed_run_input.messages,
]
}
)
if command_type == "resume":
await runtime.resume(
command=parsed_run_input,
owner_id=owner_id,
user_context=user_context,
session=session,
)
elif command_type == "run":
await runtime.run(
command=parsed_run_input,
owner_id=owner_id,
user_context=user_context,
session=session,
)
await runtime.run(
run_input=run_input,
context_messages=context_messages,
user_context=user_context,
)
logger.info(
"agentscope runtime task completed",
command_type=command_type,
thread_id=parsed_run_input.thread_id,
run_id=parsed_run_input.run_id,
thread_id=thread_id,
run_id=run_id,
)
return {
"thread_id": parsed_run_input.thread_id,
"run_id": parsed_run_input.run_id,
"thread_id": thread_id,
"run_id": run_id,
"status": "completed",
}