feat: 添加 points_audit_ledger 及 JSON 字段 Pydantic Schema 约束
This commit is contained in:
@@ -9,6 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models.agent_chat_message import AgentChatMessage
|
||||
from models.agent_chat_session import AgentChatSession
|
||||
from schemas.domain.chat_session import SessionStateSnapshot
|
||||
from schemas.enums import AgentChatMessageRole, AgentChatSessionStatus
|
||||
|
||||
|
||||
@@ -80,13 +81,13 @@ class SessionRepository:
|
||||
*,
|
||||
chat_session: AgentChatSession,
|
||||
status: AgentChatSessionStatus,
|
||||
state_snapshot: dict[str, object],
|
||||
state_snapshot: SessionStateSnapshot,
|
||||
message_delta: int,
|
||||
token_delta: int = 0,
|
||||
cost_delta: Decimal = Decimal("0"),
|
||||
) -> None:
|
||||
chat_session.status = status
|
||||
chat_session.state_snapshot = state_snapshot
|
||||
chat_session.state_snapshot = state_snapshot.model_dump(mode="json")
|
||||
chat_session.last_activity_at = datetime.now(timezone.utc)
|
||||
chat_session.message_count += message_delta
|
||||
chat_session.total_tokens += token_delta
|
||||
|
||||
@@ -16,6 +16,7 @@ from schemas.agent.system_agent import AgentType
|
||||
from schemas.agent.runtime_models import AgentOutput, FollowUpOutput, ToolAgentOutput
|
||||
from schemas.agent.visibility import SystemVisibilityBit, bit_mask
|
||||
from schemas.domain.chat_message import AgentChatMessageMetadata
|
||||
from schemas.domain.chat_session import SessionStateSnapshot
|
||||
|
||||
|
||||
class EventStore(Protocol):
|
||||
@@ -382,11 +383,12 @@ class SqlAlchemyEventStore:
|
||||
token_delta: int = 0,
|
||||
cost_delta: Decimal = Decimal("0"),
|
||||
) -> None:
|
||||
snapshot = (
|
||||
snapshot_raw = (
|
||||
chat_session.state_snapshot
|
||||
if isinstance(chat_session.state_snapshot, dict)
|
||||
else {}
|
||||
)
|
||||
snapshot = SessionStateSnapshot.model_validate(snapshot_raw)
|
||||
await session_repo.update_runtime_state(
|
||||
chat_session=chat_session,
|
||||
status=status,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
from typing import Any, cast
|
||||
@@ -53,7 +54,7 @@ _RUNTIME_AGENT_OUTPUT_ADAPTER = TypeAdapter(RuntimeAgentOutput)
|
||||
|
||||
def _serialize_tool_agent_output(
|
||||
*,
|
||||
metadata: AgentChatMessageMetadata | dict[str, object] | None,
|
||||
metadata: AgentChatMessageMetadata | None,
|
||||
) -> str | None:
|
||||
if metadata is None:
|
||||
return None
|
||||
@@ -80,30 +81,13 @@ def _serialize_tool_agent_output(
|
||||
|
||||
def _serialize_assistant_context_from_metadata(
|
||||
*,
|
||||
metadata: AgentChatMessageMetadata | dict[str, object] | None,
|
||||
metadata: AgentChatMessageMetadata | None,
|
||||
fallback_content: str,
|
||||
) -> str:
|
||||
if metadata is None:
|
||||
return fallback_content
|
||||
|
||||
try:
|
||||
resolved_metadata = (
|
||||
metadata
|
||||
if isinstance(metadata, AgentChatMessageMetadata)
|
||||
else AgentChatMessageMetadata.model_validate(metadata)
|
||||
)
|
||||
except Exception:
|
||||
return fallback_content
|
||||
|
||||
agent_output = resolved_metadata.agent_output
|
||||
if agent_output is None and isinstance(metadata, dict):
|
||||
raw = metadata.get("agent_output")
|
||||
if raw is not None:
|
||||
try:
|
||||
agent_output = _RUNTIME_AGENT_OUTPUT_ADAPTER.validate_python(raw)
|
||||
except Exception:
|
||||
return fallback_content
|
||||
|
||||
agent_output = metadata.agent_output
|
||||
if agent_output is None:
|
||||
return fallback_content
|
||||
|
||||
@@ -226,11 +210,11 @@ async def _build_recent_context_messages(
|
||||
content_raw = msg.get("content", "")
|
||||
content: str = content_raw if isinstance(content_raw, str) else ""
|
||||
metadata_raw = msg.get("metadata")
|
||||
metadata: AgentChatMessageMetadata | dict[str, object] | None
|
||||
metadata: AgentChatMessageMetadata | None
|
||||
if isinstance(metadata_raw, AgentChatMessageMetadata):
|
||||
metadata = metadata_raw
|
||||
elif isinstance(metadata_raw, dict):
|
||||
metadata = metadata_raw
|
||||
metadata = AgentChatMessageMetadata.model_validate(metadata_raw)
|
||||
else:
|
||||
metadata = None
|
||||
|
||||
@@ -391,6 +375,7 @@ async def run_agentscope_task(command: dict[str, Any]) -> dict[str, object]:
|
||||
context_config=runtime_config.context,
|
||||
)
|
||||
|
||||
points_service = PointsService(repository=PointsRepository(session))
|
||||
try:
|
||||
await runtime.run(
|
||||
run_input=run_input,
|
||||
@@ -399,15 +384,36 @@ async def run_agentscope_task(command: dict[str, Any]) -> dict[str, object]:
|
||||
runtime_config=runtime_config,
|
||||
cancel_checker=_cancel_checker,
|
||||
)
|
||||
|
||||
points_service = PointsService(repository=PointsRepository(session))
|
||||
await points_service.consume_successful_run_points(
|
||||
user_id=owner_id,
|
||||
session_id=UUID(thread_id),
|
||||
run_id=run_id,
|
||||
operator_id=owner_id,
|
||||
user_email=owner_email,
|
||||
)
|
||||
await session.commit()
|
||||
except asyncio.CancelledError:
|
||||
await points_service.record_failed_run_platform_cost(
|
||||
user_id=owner_id,
|
||||
session_id=UUID(thread_id),
|
||||
run_id=run_id,
|
||||
operator_id=owner_id,
|
||||
user_email=owner_email,
|
||||
failure_kind="canceled",
|
||||
)
|
||||
await session.commit()
|
||||
raise
|
||||
except Exception:
|
||||
await points_service.record_failed_run_platform_cost(
|
||||
user_id=owner_id,
|
||||
session_id=UUID(thread_id),
|
||||
run_id=run_id,
|
||||
operator_id=owner_id,
|
||||
user_email=owner_email,
|
||||
failure_kind="failed",
|
||||
)
|
||||
await session.commit()
|
||||
raise
|
||||
finally:
|
||||
delete_fn = getattr(redis_client, "delete", None)
|
||||
if callable(delete_fn):
|
||||
|
||||
@@ -207,6 +207,22 @@ class AgentRuntimeSettings(BaseModel):
|
||||
attachment_content_cache_max_base64_bytes: int = 262144
|
||||
|
||||
|
||||
class PointsPolicySettings(BaseModel):
|
||||
register_bonus_points: int = Field(default=60, ge=0, le=1_000_000)
|
||||
register_bonus_hmac_key: SecretStr = SecretStr("")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_hmac_key(self) -> "PointsPolicySettings":
|
||||
key = self.register_bonus_hmac_key.get_secret_value().strip()
|
||||
if not key:
|
||||
raise ValueError("points_policy.register_bonus_hmac_key must not be empty")
|
||||
if key.upper() == "CHANGE_ME":
|
||||
raise ValueError(
|
||||
"points_policy.register_bonus_hmac_key must not be CHANGE_ME"
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
def _resolve_env_file() -> str:
|
||||
current = Path(__file__).resolve()
|
||||
for parent in [current, *current.parents]:
|
||||
@@ -233,6 +249,7 @@ class Settings(BaseSettings):
|
||||
test: TestSettings = Field(default_factory=TestSettings)
|
||||
taskiq: TaskiqSettings = Field(default_factory=TaskiqSettings)
|
||||
agent_runtime: AgentRuntimeSettings = Field(default_factory=AgentRuntimeSettings)
|
||||
points_policy: PointsPolicySettings = Field(default_factory=PointsPolicySettings)
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user