feat: 增强日历功能并集成 AgentScope 代理服务
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
from core.agentscope.runtime.orchestrator import AgentScopeRuntimeOrchestrator
|
||||
from core.agentscope.runtime.react_runner import AgentScopeReActRunner
|
||||
|
||||
__all__ = ["AgentScopeRuntimeOrchestrator", "AgentScopeReActRunner"]
|
||||
@@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.agent.domain.system_agent_config import SystemAgentLLMConfig
|
||||
from models.llm import Llm
|
||||
from models.llm_factory import LlmFactory
|
||||
from models.system_agents import SystemAgents
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RuntimeStageConfig:
|
||||
stage: str
|
||||
model_code: str
|
||||
provider_name: str
|
||||
llm_config: SystemAgentLLMConfig
|
||||
|
||||
|
||||
_LEGACY_AGENT_TYPE_TO_STAGE: dict[str, str] = {
|
||||
"INTENT_RECOGNITION": "intent",
|
||||
"TASK_EXECUTION": "execution",
|
||||
"RESULT_REPORTING": "report",
|
||||
}
|
||||
|
||||
|
||||
def _normalize_stage(raw_agent_type: str) -> str | None:
|
||||
lowered = raw_agent_type.strip().lower()
|
||||
if lowered in {"intent", "execution", "report"}:
|
||||
return lowered
|
||||
return _LEGACY_AGENT_TYPE_TO_STAGE.get(raw_agent_type.strip().upper())
|
||||
|
||||
|
||||
async def load_runtime_stage_configs(
|
||||
*, session: AsyncSession
|
||||
) -> dict[str, RuntimeStageConfig]:
|
||||
stmt = (
|
||||
select(
|
||||
SystemAgents.agent_type,
|
||||
Llm.model_code,
|
||||
LlmFactory.name,
|
||||
SystemAgents.config,
|
||||
)
|
||||
.join(Llm, Llm.id == SystemAgents.llm_id)
|
||||
.join(LlmFactory, LlmFactory.id == Llm.factory_id)
|
||||
.where(SystemAgents.status == "active")
|
||||
)
|
||||
rows = (await session.execute(stmt)).all()
|
||||
by_stage: dict[str, RuntimeStageConfig] = {}
|
||||
for agent_type, model_code, provider_name, raw_config in rows:
|
||||
stage = _normalize_stage(str(agent_type))
|
||||
if stage is None:
|
||||
continue
|
||||
if stage in by_stage:
|
||||
raise ValueError(f"duplicate active system agent config for stage: {stage}")
|
||||
llm_config = SystemAgentLLMConfig.model_validate(raw_config or {})
|
||||
by_stage[stage] = RuntimeStageConfig(
|
||||
stage=stage,
|
||||
model_code=str(model_code),
|
||||
provider_name=str(provider_name),
|
||||
llm_config=llm_config,
|
||||
)
|
||||
|
||||
missing = [
|
||||
stage for stage in ("intent", "execution", "report") if stage not in by_stage
|
||||
]
|
||||
if missing:
|
||||
raise ValueError(
|
||||
f"missing active system agent configs for stages: {','.join(missing)}"
|
||||
)
|
||||
return by_stage
|
||||
@@ -0,0 +1,189 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Awaitable, Callable
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.agent.domain.user_context import UserAgentContext
|
||||
from core.agentscope.prompts import (
|
||||
build_execution_user_prompt,
|
||||
build_intent_user_prompt,
|
||||
build_report_user_prompt,
|
||||
build_system_prompt,
|
||||
)
|
||||
from core.agentscope.runtime.config_loader import (
|
||||
RuntimeStageConfig,
|
||||
load_runtime_stage_configs,
|
||||
)
|
||||
from core.agentscope.runtime.react_runner import AgentScopeReActRunner
|
||||
from core.agentscope.schemas import (
|
||||
ExecutionBatchOutput,
|
||||
ExecutionTaskOutput,
|
||||
IntentOutput,
|
||||
ReportOutput,
|
||||
RuntimeOutput,
|
||||
)
|
||||
from core.agentscope.tools.toolkit import build_stage_toolkit
|
||||
|
||||
|
||||
def _tools_payload_from_schema(
|
||||
schemas: list[dict[str, object]],
|
||||
) -> list[dict[str, object]]:
|
||||
payload: list[dict[str, object]] = []
|
||||
for item in schemas:
|
||||
function = item.get("function")
|
||||
if not isinstance(function, dict):
|
||||
continue
|
||||
name = function.get("name")
|
||||
if not isinstance(name, str) or not name:
|
||||
continue
|
||||
description = function.get("description")
|
||||
parameters = function.get("parameters")
|
||||
payload.append(
|
||||
{
|
||||
"name": name,
|
||||
"description": description if isinstance(description, str) else "",
|
||||
"parameters": (
|
||||
parameters if isinstance(parameters, dict) else {"type": "object"}
|
||||
),
|
||||
}
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
class AgentScopeRuntimeOrchestrator:
|
||||
_runner: Any
|
||||
_config_loader: Callable[[AsyncSession], Awaitable[dict[str, RuntimeStageConfig]]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
runner: Any | None = None,
|
||||
config_loader: Callable[
|
||||
[AsyncSession], Awaitable[dict[str, RuntimeStageConfig]]
|
||||
]
|
||||
| None = None,
|
||||
) -> None:
|
||||
self._runner = runner or AgentScopeReActRunner()
|
||||
if config_loader is not None:
|
||||
self._config_loader = config_loader
|
||||
else:
|
||||
self._config_loader = self._default_config_loader
|
||||
|
||||
@staticmethod
|
||||
async def _default_config_loader(
|
||||
session: AsyncSession,
|
||||
) -> dict[str, RuntimeStageConfig]:
|
||||
return await load_runtime_stage_configs(session=session)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
*,
|
||||
session: AsyncSession,
|
||||
owner_id: UUID,
|
||||
user_token: str,
|
||||
user_context: UserAgentContext,
|
||||
user_input: str | list[dict[str, Any]],
|
||||
) -> RuntimeOutput:
|
||||
stage_config = await self._config_loader(session)
|
||||
|
||||
intent_toolkit = build_stage_toolkit(
|
||||
stage="intent",
|
||||
session=session,
|
||||
owner_id=owner_id,
|
||||
user_token=user_token,
|
||||
enable_hitl=False,
|
||||
)
|
||||
intent_tools_schema = intent_toolkit.get_json_schemas()
|
||||
intent_prompt = build_system_prompt(
|
||||
stage="intent",
|
||||
user_context=user_context,
|
||||
tools=_tools_payload_from_schema(intent_tools_schema),
|
||||
)
|
||||
intent_payload = await self._runner.run_json_stage(
|
||||
stage_config=stage_config["intent"],
|
||||
agent_name="intent-agent",
|
||||
system_prompt=intent_prompt,
|
||||
user_prompt=build_intent_user_prompt(user_input=user_input),
|
||||
toolkit=intent_toolkit,
|
||||
)
|
||||
intent_output = IntentOutput.model_validate(intent_payload)
|
||||
|
||||
execution_output: ExecutionBatchOutput | None = None
|
||||
if intent_output.route == "TASK_EXECUTION":
|
||||
execution_toolkit = build_stage_toolkit(
|
||||
stage="execution",
|
||||
session=session,
|
||||
owner_id=owner_id,
|
||||
user_token=user_token,
|
||||
enable_hitl=True,
|
||||
)
|
||||
execution_tools_schema = execution_toolkit.get_json_schemas()
|
||||
execution_prompt = build_system_prompt(
|
||||
stage="execution",
|
||||
user_context=user_context,
|
||||
tools=_tools_payload_from_schema(execution_tools_schema),
|
||||
)
|
||||
|
||||
task_results: list[ExecutionTaskOutput] = []
|
||||
for task in intent_output.tasks:
|
||||
task_payload = await self._runner.run_json_stage(
|
||||
stage_config=stage_config["execution"],
|
||||
agent_name="execution-agent",
|
||||
system_prompt=execution_prompt,
|
||||
user_prompt=build_execution_user_prompt(
|
||||
task_id=task.task_id,
|
||||
task_title=task.title,
|
||||
task_objective=task.objective,
|
||||
user_input=user_input,
|
||||
intent_summary=intent_output.intent_summary,
|
||||
),
|
||||
toolkit=execution_toolkit,
|
||||
)
|
||||
if "task_id" not in task_payload:
|
||||
task_payload["task_id"] = task.task_id
|
||||
task_results.append(ExecutionTaskOutput.model_validate(task_payload))
|
||||
|
||||
statuses = {item.status for item in task_results}
|
||||
if statuses == {"SUCCESS"}:
|
||||
overall_status = "SUCCESS"
|
||||
elif "FAILED" in statuses:
|
||||
overall_status = "PARTIAL" if "SUCCESS" in statuses else "FAILED"
|
||||
else:
|
||||
overall_status = "PARTIAL"
|
||||
|
||||
execution_output = ExecutionBatchOutput(
|
||||
task_results=task_results,
|
||||
overall_status=overall_status,
|
||||
aggregate_summary="; ".join(
|
||||
item.execution_summary for item in task_results
|
||||
),
|
||||
)
|
||||
|
||||
report_prompt = build_system_prompt(
|
||||
stage="report",
|
||||
user_context=user_context,
|
||||
tools=[],
|
||||
)
|
||||
report_payload = await self._runner.run_json_stage(
|
||||
stage_config=stage_config["report"],
|
||||
agent_name="report-agent",
|
||||
system_prompt=report_prompt,
|
||||
user_prompt=build_report_user_prompt(
|
||||
user_input=user_input,
|
||||
intent_payload=intent_output.model_dump(mode="json"),
|
||||
execution_payload=(
|
||||
execution_output.model_dump(mode="json")
|
||||
if execution_output
|
||||
else None
|
||||
),
|
||||
),
|
||||
toolkit=None,
|
||||
)
|
||||
report_output = ReportOutput.model_validate(report_payload)
|
||||
return RuntimeOutput(
|
||||
intent=intent_output,
|
||||
execution=execution_output,
|
||||
report=report_output,
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, cast
|
||||
|
||||
from core.agentscope.runtime.config_loader import RuntimeStageConfig
|
||||
from core.config.settings import config
|
||||
from core.logging import get_logger
|
||||
|
||||
logger = get_logger("core.agentscope.runtime.react_runner")
|
||||
|
||||
|
||||
def _to_litellm_model(*, provider_name: str, model_code: str) -> str:
|
||||
normalized_model = model_code.strip()
|
||||
if "/" in normalized_model:
|
||||
return normalized_model
|
||||
return f"{provider_name.strip().lower()}/{normalized_model}"
|
||||
|
||||
|
||||
def _parse_json_text(raw_text: str) -> dict[str, Any]:
|
||||
text = raw_text.strip()
|
||||
if text.startswith("```"):
|
||||
text = text.strip("`")
|
||||
if text.startswith("json"):
|
||||
text = text[4:].strip()
|
||||
parsed = json.loads(text)
|
||||
if not isinstance(parsed, dict):
|
||||
raise ValueError("model output must be a JSON object")
|
||||
return cast(dict[str, Any], parsed)
|
||||
|
||||
|
||||
class AgentScopeReActRunner:
|
||||
def _build_model(self, *, stage_config: RuntimeStageConfig) -> Any:
|
||||
from agentscope.model import OpenAIChatModel
|
||||
from agentscope.types import JSONSerializableObject
|
||||
|
||||
generate_kwargs: dict[str, JSONSerializableObject] = {
|
||||
"response_format": {"type": "json_object"},
|
||||
}
|
||||
if stage_config.llm_config.temperature is not None:
|
||||
generate_kwargs["temperature"] = stage_config.llm_config.temperature
|
||||
if stage_config.llm_config.max_tokens is not None:
|
||||
generate_kwargs["max_tokens"] = stage_config.llm_config.max_tokens
|
||||
if stage_config.llm_config.timeout_seconds is not None:
|
||||
generate_kwargs["timeout"] = stage_config.llm_config.timeout_seconds
|
||||
|
||||
return OpenAIChatModel(
|
||||
model_name=_to_litellm_model(
|
||||
provider_name=stage_config.provider_name,
|
||||
model_code=stage_config.model_code,
|
||||
),
|
||||
api_key=config.litellm.api_key,
|
||||
stream=False,
|
||||
client_kwargs={"base_url": config.litellm.base_url},
|
||||
generate_kwargs=cast(dict[str, JSONSerializableObject], generate_kwargs),
|
||||
)
|
||||
|
||||
async def run_json_stage(
|
||||
self,
|
||||
*,
|
||||
stage_config: RuntimeStageConfig,
|
||||
agent_name: str,
|
||||
system_prompt: str,
|
||||
user_prompt: str,
|
||||
toolkit: Any | None,
|
||||
) -> dict[str, Any]:
|
||||
from agentscope.agent import ReActAgent
|
||||
from agentscope.formatter import OpenAIChatFormatter
|
||||
from agentscope.memory import InMemoryMemory
|
||||
from agentscope.message import Msg
|
||||
|
||||
agent = ReActAgent(
|
||||
name=agent_name,
|
||||
sys_prompt=system_prompt,
|
||||
model=self._build_model(stage_config=stage_config),
|
||||
formatter=OpenAIChatFormatter(),
|
||||
toolkit=toolkit,
|
||||
memory=InMemoryMemory(),
|
||||
max_iters=6,
|
||||
)
|
||||
try:
|
||||
response = await agent(Msg(name="user", content=user_prompt, role="user"))
|
||||
text_content = response.get_text_content() or "{}"
|
||||
return _parse_json_text(text_content)
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.exception(
|
||||
"agentscope stage output is not valid json",
|
||||
stage=stage_config.stage,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
raise RuntimeError("agent output format invalid") from exc
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"agentscope stage execution failed",
|
||||
stage=stage_config.stage,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
raise RuntimeError("agent execution failed") from exc
|
||||
Reference in New Issue
Block a user