Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1139,8 +1139,8 @@ private void markWithheld(final Map<String, Object> content, final SessionDataFi
}
}

private static void fill(final Map<String, Object> content, final Map<String, Object> tool,
final SessionDataFile.Record rec, @Nullable final Long block) {
static void fill(final Map<String, Object> content, final Map<String, Object> 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));
Expand All @@ -1165,12 +1165,10 @@ private static void fill(final Map<String, Object> content, final Map<String, Ob
if (StringUtil.isNotEmpty(p.getText())) {
content.put("text", clip(p.getText()));
} else if (data != null) {
final String t = readable(rec);
if (!t.isEmpty() && !t.equals(data.trim())) {
content.put("text", clip(t));
} else {
content.put("text", clip(data));
}
// a queued command reads as the prompt inside this part's own data, and any other data reads as itself: a
// tool call that shares a record with the agent's text reads as its arguments, never as that text
final String prompt = queuedPrompt(data);
content.put("text", clip(prompt != null ? prompt : data));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package org.apache.skywalking.oap.server.ai.agent.conversation.view;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.apache.skywalking.oap.server.ai.agent.conversation.format.SessionDataFile;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -88,12 +90,36 @@ public void aLongNameIsCutOnACharacterBoundary() {
}
}

/**
* A step on one part reads that part alone: a tool call that shares a record with the agent's text reads as its
* arguments, never as that text, and a queued command kept as data beside other text reads as its own prompt.
*/
@Test
public void aStepOnAPartReadsThatPart() {
final String args = "{\"command\":\"kubectl logs api-7f9c\"}";
final Map<String, Object> 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<String, Object> 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);
}
Expand Down
Loading