From 8d01396130eb12fa7fe0f86faf3f4b1e6ecdd09a Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 25 May 2026 20:31:09 +0800 Subject: [PATCH] fix(conversation): capture runtime model/provider with token usage in IM and webchat paths --- .../vip/mate/channel/ChannelMessageRouter.java | 18 ++++++++++++++---- .../channel/webchat/WebChatController.java | 9 +++++++-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java b/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java index 5abd02a5..f3bbbeef 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java @@ -732,8 +732,9 @@ public class ChannelMessageRouter { // for any Web SSE viewer of the same conversationId. StringBuilder replyAccumulator = new StringBuilder(); final String channelType = adapter.getChannelType(); - // Token usage: capture _usage_final event emitted at stream end + // Token usage + model attribution: capture _usage_final event emitted at stream end final int[] usage = {0, 0}; // [promptTokens, completionTokens] + final String[] modelInfo = {null, null}; // [runtimeModel, runtimeProvider] agentService.chatStructuredStream(agentId, promptText, conversationId, message.getSenderId(), chatOrigin) .doOnNext(delta -> { @@ -742,6 +743,10 @@ public class ChannelMessageRouter { Map data = delta.eventData(); usage[0] = ((Number) data.getOrDefault("promptTokens", 0)).intValue(); usage[1] = ((Number) data.getOrDefault("completionTokens", 0)).intValue(); + Object model = data.get("runtimeModelName"); + Object provider = data.get("runtimeProviderId"); + if (model != null) modelInfo[0] = model.toString(); + if (provider != null) modelInfo[1] = provider.toString(); } mirrorPlanEventToTracker(conversationId, delta, channelType); } else if (delta.content() != null) { @@ -778,7 +783,7 @@ public class ChannelMessageRouter { String status = isError ? "error" : "completed"; MessageEntity saved = conversationService.saveMessage( conversationId, "assistant", reply, null, status, - usage[0], usage[1], null, null); + usage[0], usage[1], modelInfo[0], modelInfo[1]); savedAssistantId = saved != null ? saved.getId() : null; if (!isError) { publishConversationCompletedEvent(agentId, conversationId, message.getContent(), reply); @@ -892,13 +897,18 @@ public class ChannelMessageRouter { // only reads `delta.content()` and would otherwise eat plan_created / // plan_step_* events, leaving the Web Console mirror with no // PlanStepsPanel for IM-routed conversations. - // Token usage: capture _usage_final event emitted at stream end + // Token usage + model attribution: capture _usage_final event emitted at stream end final int[] usage = {0, 0}; // [promptTokens, completionTokens] + final String[] modelInfo = {null, null}; // [runtimeModel, runtimeProvider] Flux mirroredStream = stream.doOnNext(delta -> { if (delta.isEvent() && "_usage_final".equals(delta.eventType())) { Map data = delta.eventData(); usage[0] = ((Number) data.getOrDefault("promptTokens", 0)).intValue(); usage[1] = ((Number) data.getOrDefault("completionTokens", 0)).intValue(); + Object model = data.get("runtimeModelName"); + Object provider = data.get("runtimeProviderId"); + if (model != null) modelInfo[0] = model.toString(); + if (provider != null) modelInfo[1] = provider.toString(); } mirrorPlanEventToTracker(conversationId, delta, channelType); }); @@ -922,7 +932,7 @@ public class ChannelMessageRouter { String status = isError ? "error" : "completed"; MessageEntity saved = conversationService.saveMessage( conversationId, "assistant", finalContent, null, status, - usage[0], usage[1], null, null); + usage[0], usage[1], modelInfo[0], modelInfo[1]); if (!isError) { publishConversationCompletedEvent(agentId, conversationId, promptText, finalContent); } diff --git a/mateclaw-server/src/main/java/vip/mate/channel/webchat/WebChatController.java b/mateclaw-server/src/main/java/vip/mate/channel/webchat/WebChatController.java index 4f3ea9ff..779fd1cc 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/webchat/WebChatController.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/webchat/WebChatController.java @@ -118,8 +118,9 @@ public class WebChatController { // Pattern mirrors ChatController: always accumulate, only broadcast when the // delta is not a persistence-only echo of content already streamed by inner nodes. StringBuilder assistantReply = new StringBuilder(); - // Token usage: capture _usage_final event emitted at stream end + // Token usage + model attribution: capture _usage_final event emitted at stream end final int[] usage = {0, 0}; // [promptTokens, completionTokens] + final String[] modelInfo = {null, null}; // [runtimeModel, runtimeProvider] agentService.chatStructuredStream(agentId, message, conversationId, visitorId) .doOnNext(delta -> { @@ -127,6 +128,10 @@ public class WebChatController { Map data = delta.eventData(); usage[0] = ((Number) data.getOrDefault("promptTokens", 0)).intValue(); usage[1] = ((Number) data.getOrDefault("completionTokens", 0)).intValue(); + Object model = data.get("runtimeModelName"); + Object provider = data.get("runtimeProviderId"); + if (model != null) modelInfo[0] = model.toString(); + if (provider != null) modelInfo[1] = provider.toString(); } if (delta.content() != null && !delta.content().isEmpty()) { assistantReply.append(delta.content()); @@ -147,7 +152,7 @@ public class WebChatController { if (!reply.isBlank()) { conversationService.saveMessage( conversationId, "assistant", reply, List.of(), - "completed", usage[0], usage[1], null, null); + "completed", usage[0], usage[1], modelInfo[0], modelInfo[1]); } completionPublisher.publish( agentId, conversationId, message, reply, "webchat");