platform-core/modules/serviceCenter/services/serviceAgent/datamodelAgent.py
ValueOn AG 4a60086c80
Some checks failed
Deploy Plattform-Core (Int) / test (push) Failing after 15s
Deploy Plattform-Core (Int) / deploy (push) Has been skipped
cp adapted to 2026 poweron
2026-06-09 09:53:31 +02:00

218 lines
7.4 KiB
Python

# Copyright (c) 2026 PowerOn AG
# All rights reserved.
"""Data models for the Agent service."""
from typing import List, Dict, Any, Optional
from enum import Enum
from pydantic import BaseModel, Field
from modules.shared.timeUtils import getUtcTimestamp
from modules.datamodels.datamodelAi import OperationTypeEnum, PriorityEnum
import uuid
class AgentStatusEnum(str, Enum):
RUNNING = "running"
COMPLETED = "completed"
MAX_ROUNDS_REACHED = "maxRoundsReached"
BUDGET_EXCEEDED = "budgetExceeded"
ERROR = "error"
STOPPED = "stopped"
class AgentEventTypeEnum(str, Enum):
MESSAGE = "message"
CHUNK = "chunk"
TOOL_CALL = "toolCall"
TOOL_RESULT = "toolResult"
AGENT_PROGRESS = "agentProgress"
AGENT_SUMMARY = "agentSummary"
FILE_CREATED = "fileCreated"
FILE_UPDATED = "fileUpdated"
FILE_EDIT_PROPOSAL = "fileEditProposal"
FILE_VERSION = "fileVersion"
FILE_EDIT_REJECTED = "fileEditRejected"
DATA_SOURCE_ACCESS = "dataSourceAccess"
VOICE_RESPONSE = "voiceResponse"
REVEAL_DOWNLOAD = "revealDownload"
FINAL = "final"
ERROR = "error"
class ToolDefinition(BaseModel):
"""Schema for a tool available to the agent."""
name: str = Field(description="Unique tool name")
description: str = Field(description="What this tool does")
displayLabel: Optional[str] = Field(
default=None,
description="Short human-readable activity phrase (e.g. 'researching on the web'). "
"Used for live progress messages in meetings. English gerund phrase; "
"localised by the caller."
)
parameters: Dict[str, Any] = Field(
default_factory=dict,
description="JSON Schema for tool parameters"
)
readOnly: bool = Field(
default=False,
description="If True, tool can run in parallel with other readOnly tools"
)
featureType: Optional[str] = Field(
default=None,
description="Feature scope for this tool (None = available to all)"
)
toolSet: Optional[str] = Field(
default=None,
description="Tool-set scope (None = available to all sets, e.g. 'core', 'workspace')"
)
class ToolCallRequest(BaseModel):
"""A tool call requested by the AI model."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
name: str
args: Dict[str, Any] = Field(default_factory=dict)
class ToolResult(BaseModel):
"""Result from executing a tool."""
toolCallId: str
toolName: str
success: bool = True
data: str = ""
error: Optional[str] = None
errorDetails: Optional[Dict[str, Any]] = Field(
default=None,
description=(
"Structured, machine-readable error payload for the LLM (e.g. validation "
"repair hints with code/field/suggestion/hint). `error` remains the short "
"human-readable text for logs and audit."
),
)
durationMs: int = 0
sideEvents: Optional[List[Dict[str, Any]]] = None
class AgentEvent(BaseModel):
"""Event emitted during agent execution for SSE streaming."""
type: AgentEventTypeEnum
content: Optional[str] = None
data: Optional[Dict[str, Any]] = None
class AgentConfig(BaseModel):
"""Configuration for an agent run."""
maxRounds: int = Field(default=25, ge=1, le=100)
maxCostCHF: Optional[float] = Field(default=None, ge=0.0)
toolSet: str = Field(default="core")
initialToolboxes: List[str] = Field(default_factory=lambda: ["core"])
availableToolboxes: List[str] = Field(default_factory=list)
temperature: Optional[float] = Field(default=None, ge=0.0, le=2.0)
operationType: Optional[OperationTypeEnum] = Field(default=None, description="Override the default AGENT operationType for model selection")
excludeActionTools: bool = Field(
default=False,
description=(
"If True, do NOT register workflow-action methods as agent tools. "
"Used by editor-style agents (e.g. WorkflowAutomation) that should only "
"manipulate the workflow graph, not execute its actions."
),
)
excludeAllTools: bool = Field(
default=False,
description=(
"If True, send no tool definitions to the LLM at all. "
"Used for pure conversational turns (e.g. CommCoach coaching chat) "
"where tools are not needed and would only add latency."
),
)
priority: Optional[PriorityEnum] = Field(
default=None,
description="Model selection priority: speed | quality | cost | balanced. None = use default (balanced).",
)
class AgentState(BaseModel):
"""Tracks state across an agent loop execution."""
workflowId: str
currentRound: int = 0
maxRounds: int = 25
totalAiCalls: int = 0
totalToolCalls: int = 0
totalCostCHF: float = 0.0
totalProcessingTime: float = 0.0
status: AgentStatusEnum = AgentStatusEnum.RUNNING
abortReason: Optional[str] = None
class ToolCallLog(BaseModel):
"""Log of a single tool call for observability."""
toolName: str
args: Dict[str, Any] = Field(default_factory=dict)
success: bool = True
durationMs: int = 0
error: Optional[str] = None
validationFailureCode: Optional[str] = Field(
default=None,
description=(
"If the tool call was rejected by a pre-execute validator (e.g. "
"QueryValidator), the structured error code (e.g. FIELD_NOT_FOUND). "
"None when the call ran cleanly or failed for other reasons."
),
)
resultData: str = Field(default="", description="Short result summary for artifact tracking")
class AgentRoundLog(BaseModel):
"""Log of a single agent round for observability."""
roundNumber: int
aiModel: str = ""
inputTokens: int = 0
outputTokens: int = 0
costCHF: float = 0.0
toolCalls: List[ToolCallLog] = Field(default_factory=list)
durationMs: int = 0
class AgentTrace(BaseModel):
"""Full trace of an agent workflow for observability."""
workflowId: str
userId: str = ""
featureInstanceId: str = ""
startedAt: float = Field(default_factory=getUtcTimestamp)
completedAt: Optional[float] = None
status: AgentStatusEnum = AgentStatusEnum.RUNNING
totalRounds: int = 0
totalToolCalls: int = 0
totalCostCHF: float = 0.0
abortReason: Optional[str] = None
validationFailures: int = Field(
default=0,
description="Total tool calls rejected by a pre-execute validator across the run.",
)
repairAttempts: int = Field(
default=0,
description=(
"Number of times the LLM retried a previously rejected tool (same toolName) "
"in a later round. Counted by `agentLoop` from per-round ToolCallLog entries."
),
)
successAfterRepair: int = Field(
default=0,
description=(
"Number of repair attempts that produced a clean (validationFailureCode=None) "
"result. Combined with `repairAttempts` this gives the repair conversion rate."
),
)
rounds: List[AgentRoundLog] = Field(default_factory=list)
class PendingFileEdit(BaseModel):
"""A proposed file edit awaiting user approval."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
fileId: str
fileName: str
mimeType: str = ""
oldContent: str = ""
newContent: str = ""
status: str = Field(default="pending", description="pending | accepted | rejected")
toolCallId: str = ""
workflowId: str = ""