refactor: 简化 AgentScope 运行时模块与 prompt 系统
This commit is contained in:
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user