ab967d1c by Ean Schuessler

Refactor Agent Runtime: General-purpose LLM Request Service

BREAKING: Introduces new LLM Request/Response pattern for CSR agents

Major Changes:
- Add new SystemMessage types: SmtyLlmRequest, SmtyLlmResponse
- Extend SystemMessage entity with callback and audit fields:
  * parentSystemMessageId - Links Response → Request
  * callbackServiceName - Service to call when LLM completes
  * callbackParameters - JSON params for callback
  * sourceTypeEnumId/sourceId - Audit trail (Order, CommEvent, etc.)
  * llmResponse - Raw response stored for debugging

New Services:
- AgentServices.process#LLMRequest: General-purpose async LLM service
  * Any trigger (SECA, UI, Order, etc.) can call this
  * Creates SmtyLlmRequest SystemMessage
  * Triggers async processing via poller
  * Validates callback service exists before creating task

Refactored Services:
- AgentServices.run#AgentTaskTurn: Universal agent processor
  * Supports both SmtyAgentTask (legacy) and SmtyLlmRequest (new)
  * ALWAYS provides MCP tools to LLM (for CSR agent pattern)
  * Creates SmtyLlmResponse and calls callback for new pattern
  * Maintains backward compatibility with SmtyAgentTask

- AgentServices.callback#CommunicationEvent: Saves LLM responses to conversation
  * Callback service for CommunicationEvent-triggered requests
  * Maintains conversation thread via rootCommEventId

Updated SECA:
- AgentTriggerOnCommunication now calls AgentServices.process#LLMRequest
  * Uses callback pattern instead of direct SystemMessage creation
  * Enables full audit trail via SystemMessage Request :left_right_arrow: Response

Benefits:
- General-purpose: Any trigger can request LLM processing (orders, inventory, support, etc.)
- Traceability: Full audit trail via linked SystemMessages
- RBAC: Agent impersonates users, respects permissions
- Same UX: Agent uses same screens humans use (via MCP tools)
- Flexible: Different callbacks handle responses differently
1 parent 5c6a9826
......@@ -11,5 +11,9 @@
<!-- Agent Task Message Type -->
<moqui.service.message.SystemMessageType systemMessageTypeId="SmtyAgentTask" description="Agent Task"/>
<!-- LLM Request/Response Message Types -->
<moqui.service.message.SystemMessageType systemMessageTypeId="SmtyLlmRequest" description="LLM Request"/>
<moqui.service.message.SystemMessageType systemMessageTypeId="SmtyLlmResponse" description="LLM Response"/>
</entity-facade-xml>
......
......@@ -20,12 +20,38 @@
<description>Specific AI configuration used for this task.</description>
</field>
<field name="rootCommEventId" type="id">
<description>The root CommunicationEvent ID for the conversation thread.</description>
<description>The root CommunicationEvent ID for conversation thread.</description>
</field>
<!-- Callback and Audit Fields for LLM Request/Response Pattern -->
<field name="parentSystemMessageId" type="id">
<description>Parent SystemMessage ID (links Response to Request).</description>
</field>
<field name="callbackServiceName" type="text-medium">
<description>Service to call with LLM response when complete.</description>
</field>
<field name="callbackParameters" type="text-very-long">
<description>JSON parameters to pass to callback service.</description>
</field>
<field name="sourceTypeEnumId" type="id">
<description>Type of entity that triggered this request (Order, CommEvent, etc.).</description>
</field>
<field name="sourceId" type="id">
<description>ID of the triggering entity (orderId, communicationEventId, etc.).</description>
</field>
<field name="llmResponse" type="text-very-long">
<description>Raw LLM response content (stored for audit/debug).</description>
</field>
<relationship type="one" title="RequestedBy" related="mantle.party.Party">
<key-map field-name="requestedByPartyId" related="partyId"/>
</relationship>
<relationship type="one" title="ParentSystemMessage" related="moqui.service.message.SystemMessage">
<key-map field-name="parentSystemMessageId" related="systemMessageId"/>
</relationship>
<relationship type="many" title="ChildSystemMessages" related="moqui.service.message.SystemMessage" fk-name="SysMsgToSysMsg">
<key-map field-name="systemMessageId" related="parentSystemMessageId"/>
</relationship>
<!-- Note: effectiveUserId links to UserAccount, but we don't force FK to allow system users -->
<relationship type="one" related="mantle.product.store.ProductStore">
<key-map field-name="productStoreId"/>
......
<?xml version="1.0" encoding="UTF-8"?>
<secas xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/service-eca-3.xsd">
<!-- SECA for CommunicationEvent to Agent (uses SmtyAgentTask pattern) -->
<seca id="AgentTriggerOnCommunication" service="create#mantle.party.communication.CommunicationEvent" when="post-service">
<actions>
<script><![CDATA[
......@@ -14,16 +15,17 @@
ec.logger.info("SECA AgentTriggerOnCommunication: Creating SmtyAgentTask SystemMessage for thread ${rootId}")
// Trigger Agent Turn
ec.service.sync().name("create#moqui.service.message.SystemMessage").parameters([
systemMessageTypeId: 'SmtyAgentTask',
statusId: 'SmsgReceived',
// Create Agent Task using process#LLMRequest with callback
ec.service.async().name("AgentServices.process#LLMRequest").parameters([
prompt: body,
requestedByPartyId: fromPartyId,
effectiveUserId: ec.user.userId,
effectiveUserId: ec.user.userId,
productStoreId: 'POPC_DEFAULT',
aiConfigId: 'DEFAULT',
rootCommEventId: rootId,
isOutgoing: 'N'
callbackServiceName: "AgentServices.callback#CommunicationEvent",
callbackParameters: [rootCommEventId: rootId, communicationEventId: communicationEventId],
sourceTypeEnumId: "CommunicationEvent",
sourceId: communicationEventId
]).call()
}
]]></script>
......