weiqingy commented on code in PR #924:
URL: https://github.com/apache/flink-agents/pull/924#discussion_r3654253847


##########
plan/src/test/java/org/apache/flink/agents/plan/actions/ChatModelActionRetryTest.java:
##########
@@ -127,6 +128,129 @@ void chatSucceedsWithoutRetry_retryCountIsZero() throws 
Exception {
         verify(mockActionMetricGroup, never()).getSubGroup(anyString(), 
anyString());
     }
 
+    @Test
+    void chatReportsLlmExecution() throws Exception {
+        RunnerContext reportingCtx = reportingRunnerContext();
+        BaseChatModelSetup chatModel = 
configureReportingChatContext(reportingCtx);
+        when(chatModel.chat(any(), any(), any()))
+                .thenReturn(new ChatMessage(MessageRole.ASSISTANT, "hello"));
+
+        ChatModelAction.chat(
+                UUID.randomUUID(),
+                "test-model",
+                List.of(new ChatMessage(MessageRole.USER, "hi")),
+                Map.of(),
+                null,
+                reportingCtx);
+
+        ExecutionReporter reporter = (ExecutionReporter) reportingCtx;
+        verify(reporter)
+                .reportExecutionStarted(ExecutionReporter.EntityTypes.LLM, 
"test-model", Map.of());
+        verify(reporter)
+                .reportExecutionSucceeded(
+                        ExecutionReporter.EntityTypes.LLM, "test-model", 
Map.of());
+    }
+
+    @Test
+    void chatReportsStructuredOutputParserExecution() throws Exception {
+        RunnerContext reportingCtx = reportingRunnerContext();
+        BaseChatModelSetup chatModel = 
configureReportingChatContext(reportingCtx);
+        when(chatModel.chat(any(), any(), any()))
+                .thenReturn(new ChatMessage(MessageRole.ASSISTANT, 
"{\"answer\":\"42\"}"));
+
+        ChatModelAction.chat(
+                UUID.randomUUID(),
+                "test-model",
+                List.of(new ChatMessage(MessageRole.USER, "hi")),
+                Map.of(),
+                Map.class,
+                reportingCtx);
+
+        ExecutionReporter reporter = (ExecutionReporter) reportingCtx;
+        verify(reporter)
+                .reportExecutionStarted(
+                        ExecutionReporter.EntityTypes.PARSER, 
Agent.STRUCTURED_OUTPUT, Map.of());
+        verify(reporter)
+                .reportExecutionSucceeded(
+                        ExecutionReporter.EntityTypes.PARSER, 
Agent.STRUCTURED_OUTPUT, Map.of());
+    }
+
+    @Test
+    void chatRetriesStructuredOutputParseErrorWithoutFailingLlm() throws 
Exception {
+        RunnerContext reportingCtx = reportingRunnerContext();
+        BaseChatModelSetup chatModel = 
configureReportingChatContext(reportingCtx);
+        when(reportingCtx.getConfig())
+                .thenReturn(readableConfig(Agent.ErrorHandlingStrategy.RETRY, 
1, 0));
+        when(chatModel.chat(any(), any(), any()))
+                .thenReturn(
+                        new ChatMessage(MessageRole.ASSISTANT, "not-json"),
+                        new ChatMessage(MessageRole.ASSISTANT, 
"{\"answer\":\"42\"}"));
+
+        ChatModelAction.chat(
+                UUID.randomUUID(),
+                "test-model",
+                List.of(new ChatMessage(MessageRole.USER, "hi")),
+                Map.of(),
+                Map.class,
+                reportingCtx);
+
+        verify(chatModel, times(2)).chat(any(), any(), any());
+
+        ExecutionReporter reporter = (ExecutionReporter) reportingCtx;
+        verify(reporter, times(2))
+                .reportExecutionStarted(ExecutionReporter.EntityTypes.LLM, 
"test-model", Map.of());
+        verify(reporter, times(2))
+                .reportExecutionSucceeded(
+                        ExecutionReporter.EntityTypes.LLM, "test-model", 
Map.of());
+        verify(reporter)
+                .reportExecutionFailed(
+                        eq(ExecutionReporter.EntityTypes.PARSER),
+                        eq(Agent.STRUCTURED_OUTPUT),
+                        eq(Map.of()),
+                        any(Exception.class),
+                        
eq(ExecutionReporter.ProblemCategories.MODEL_OUTPUT_PARSE_ERROR));
+        verify(reporter, never())
+                .reportExecutionFailed(
+                        eq(ExecutionReporter.EntityTypes.LLM),
+                        eq("test-model"),
+                        eq(Map.of()),
+                        any(Throwable.class),
+                        any());
+    }
+
+    @Test
+    void chatReportsEachRetriedModelInvocation() throws Exception {

Review Comment:
   Anchoring here since this is where the contract gets pinned: 
`chatReportsEachRetriedModelInvocation` asserts `times(2)` on 
`reportExecutionStarted`. `started` sits inside the retry loop 
(`ChatModelAction.java:371-373`), and Python matches 
(`chat_model_action.py:352-375`), so both languages emit one execution per 
attempt.
   
   On the design discussion the contract landed on one execution per framework 
model invocation, with retries collapsing into it and Parser recorded 
separately. Parser is implemented that way (`:457-474`); the retry half went 
the other way.
   
   Downstream, a `retryCount` of 3 on a successful request reads as 4 LLM 
executions at a 25% success rate, and nothing in the record separates "attempt 
2" from "a second call". `ChatResponseEvent` already carries `retryCount`, so 
the two views disagree.
   
   Which way did you land in the end? Two shapes if either helps: hoist 
`started`/`succeeded` outside the loop and put attempts on the single terminal, 
or keep per-attempt and add an ordinal plus a shared correlation id. Either 
way, would it be worth pinning in `ExecutionReporter`'s javadoc, now that it's 
permanent public API?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to