diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md index 2da555b7c8d6..e154108cd1fa 100644 --- a/docs/en/changes/changes.md +++ b/docs/en/changes/changes.md @@ -68,6 +68,7 @@ * Fix RabbitMQ node singleblock allocator meters reading multiblock carriers. `meter_rabbitmq_node_allocated_singleblock_used` / `_unused` copied `kind=mbcs`, so the Singleblock charts matched Multiblock; they now use `kind=sbcs`, and `_unused` subtracts the used blocks size from the carriers size, as RabbitMQ's Erlang memory allocators dashboard does, instead of reporting the whole carriers size. Values stored before the upgrade keep the old numbers, so these charts step at the upgrade. * Fix PostgreSQL temporary-files rate reading temp bytes. `meter_pg_temporary_files_rate` / `meter_pg_instance_temporary_files_rate` used `pg_stat_database_temp_bytes`, so the Temporary Files / s charts showed byte throughput; they now use `pg_stat_database_temp_files`, and no chart shows the temporary-file byte throughput. Values stored before the upgrade keep the old numbers, so these charts step at the upgrade. * Fix Zabbix agent filesystem usage percentage reading inode usage. `meter_vm_filesystem_percentage` used `vfs.fs.inode` `pused`, so the File System Mountpoint Usage chart showed inode occupancy; it now uses the agent's `vfs.fs.size[/,pused]`, the space usage `df` and the Telegraf VM rule report, which leaves the space reserved for root out (the OTEL VM rule counts that space as used, so it reads higher on the same disk). Add `vfs.fs.size[/,pused]` to the required item keys, and drop unused `vfs.fs.inode[/,pused]` and the duplicate `vm.memory.size[pavailable]`. Values stored before the upgrade keep the old numbers, so this chart steps at the upgrade. +* AI agent conversations read a tool call as its arguments, as the AI Sessionizer does (apache/skywalking-ai-sessionizer#94). A step on one part of a record reads that part alone. A tool call that shares a record with the agent's text read as that text, and now reads as its arguments. A queued command reads as the prompt inside its own part, never as text another part of the record holds. #### UI * Add a Virtual GenAI evaluation-record page and evaluation-score chart in Horizon UI, so operators can inspect evaluation result, level, reason, judge model, timestamp, trace linkage, and the `gen_ai_model_evaluation_score_ppm` trend for evaluated records. diff --git a/oap-server/analyzer/ai-agent-conversation/src/main/java/org/apache/skywalking/oap/server/ai/agent/conversation/view/ConversationViewBuilder.java b/oap-server/analyzer/ai-agent-conversation/src/main/java/org/apache/skywalking/oap/server/ai/agent/conversation/view/ConversationViewBuilder.java index 09c06199411f..b7b0fcb691b9 100644 --- a/oap-server/analyzer/ai-agent-conversation/src/main/java/org/apache/skywalking/oap/server/ai/agent/conversation/view/ConversationViewBuilder.java +++ b/oap-server/analyzer/ai-agent-conversation/src/main/java/org/apache/skywalking/oap/server/ai/agent/conversation/view/ConversationViewBuilder.java @@ -1139,8 +1139,8 @@ private void markWithheld(final Map content, final SessionDataFi } } - private static void fill(final Map content, final Map tool, - final SessionDataFile.Record rec, @Nullable final Long block) { + static void fill(final Map content, final Map tool, + final SessionDataFile.Record rec, @Nullable final Long block) { final SessionDataFile.Part p = partAt(rec, block); if (p == null) { final String text = clip(readable(rec)); @@ -1165,12 +1165,10 @@ private static void fill(final Map content, final Map call = new HashMap<>(); + ConversationViewBuilder.fill(call, new HashMap<>(), withParts( + "{\"k\":\"text\",\"text\":\"I will look at the logs first.\"}", + "{\"k\":\"call\",\"name\":\"Bash\",\"data\":" + args + "}"), 1L); + assertEquals(args, call.get("text")); + final Map queued = new HashMap<>(); + ConversationViewBuilder.fill(queued, new HashMap<>(), withParts( + "{\"k\":\"text\",\"text\":\"unrelated prose\"}", + "{\"k\":\"data\",\"data\":{\"type\":\"queued_command\",\"prompt\":[{\"type\":\"text\",\"text\":\"restart the worker\"}]}}"), + 1L); + assertEquals("restart the worker", queued.get("text")); + } + private static SessionDataFile.Record record(final String... data) { - final StringBuilder line = new StringBuilder("{\"ord\":1,\"off\":0,\"sha\":\"0\",\"bytes\":1,\"parts\":["); + final String[] parts = new String[data.length]; for (int i = 0; i < data.length; i++) { - line.append(i == 0 ? "" : ",").append("{\"k\":\"data\",\"data\":").append(data[i]).append('}'); + parts[i] = "{\"k\":\"data\",\"data\":" + data[i] + "}"; } - line.append("]}"); + return withParts(parts); + } + + private static SessionDataFile.Record withParts(final String... parts) { + final String line = "{\"ord\":1,\"off\":0,\"sha\":\"0\",\"bytes\":1,\"parts\":[" + String.join(",", parts) + "]}"; final String file = HEADER + "\n" + line + "\n"; return SessionDataFile.parse(file.getBytes(StandardCharsets.UTF_8)).getRecords().get(0); }