mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-13 03:59:12 +00:00
fix(lens): preserve SDK actors and bound native identity size
This commit is contained in:
parent
a685616392
commit
01fcf24afd
6 changed files with 124 additions and 49 deletions
|
|
@ -1,9 +1,12 @@
|
|||
WITH
|
||||
(Framework IN ('claude-code', 'claude-agent-sdk') AND (SpanAttributes['session.id'] != '' OR SpanAttributes['agent_id'] != '')) AS native,
|
||||
coalesce(nullIf(SpanAttributes['query_source_safe'], ''), SpanAttributes['query_source']) AS native_source,
|
||||
((Framework = 'claude-code' OR (Framework = 'claude-agent-sdk' AND
|
||||
(startsWith(SpanName, 'claude_code.') OR SpanAttributes['agent_id'] != '' OR native_source != '')))
|
||||
AND (SpanAttributes['session.id'] != '' OR SpanAttributes['agent_id'] != '')) AS native,
|
||||
if(SpanAttributes['session.id'] = '', TraceId, SpanAttributes['session.id']) AS native_session,
|
||||
(native AND SpanAttributes['agent_id'] != '') AS native_child,
|
||||
(native AND NOT native_child AND (SpanName = 'claude_code.interaction' OR
|
||||
coalesce(nullIf(SpanAttributes['query_source_safe'], ''), SpanAttributes['query_source']) IN
|
||||
native_source IN
|
||||
('repl_main_thread', 'sdk', 'sdk_main_thread', 'generate_session_title', 'prompt_suggestion'))) AS native_root,
|
||||
page AS (
|
||||
SELECT TraceId AS trace_id,
|
||||
|
|
@ -45,11 +48,12 @@ LEFT JOIN (
|
|||
arraySort(groupUniqArrayIf(toString(Framework), Framework != '')) AS frameworks,
|
||||
groupUniqArrayIf(native_session, native_root) AS root_sessions,
|
||||
groupUniqArrayIf((native_session, SpanId), native_root AND SpanName = 'claude_code.interaction') AS root_interactions,
|
||||
uniqExactIf((native_session, SpanAttributes['agent_id']), native_child)
|
||||
+ length(root_sessions) AS native_count,
|
||||
groupUniqArrayIf((native_session, SpanAttributes['agent_id']), native_child) AS child_identities,
|
||||
groupUniqArrayIf((native_session, SpanAttributes['agent_id'], SpanId), native_child AND SpanName = 'claude_code.interaction') AS child_interactions,
|
||||
length(child_identities) + length(root_sessions) AS native_count,
|
||||
native_count + uniqExactIf(if(AgentName = '', SpanName, AgentName), ObservationType = 'agent' AND NOT native) AS agent_count,
|
||||
uniqExactIf(SpanId, ObservationType = 'agent' AND NOT native)
|
||||
+ uniqExactIf((native_session, SpanAttributes['agent_id']), native_child)
|
||||
+ arraySum(arrayMap(child -> greatest(1, arrayCount(interaction -> (interaction.1, interaction.2) = child, child_interactions)), child_identities))
|
||||
+ arraySum(arrayMap(session -> greatest(1, arrayCount(interaction -> interaction.1 = session, root_interactions)), root_sessions)) AS resolved_invocations
|
||||
FROM otel_traces
|
||||
WHERE Timestamp >= (SELECT min(trace_start) FROM page)
|
||||
|
|
|
|||
|
|
@ -841,6 +841,20 @@ async fn native_list_counts_each_root_session_and_does_not_count_children_as_roo
|
|||
"agent",
|
||||
"claude_code.interaction",
|
||||
),
|
||||
(
|
||||
"child-resumed",
|
||||
"session-a",
|
||||
"child",
|
||||
"agent",
|
||||
"claude_code.interaction",
|
||||
),
|
||||
(
|
||||
"legacy-sdk",
|
||||
"sdk-session",
|
||||
"",
|
||||
"agent",
|
||||
"ClaudeAgentSDK.query",
|
||||
),
|
||||
];
|
||||
insert_rows(client, &writer, DATABASE, InsertTable::OtelTraces, records.into_iter().map(|(id, session, actor, kind, name)| BTreeMap::from([
|
||||
("Timestamp".into(), json!(1_790_000_000_000_000_000_i64)),
|
||||
|
|
@ -848,9 +862,9 @@ async fn native_list_counts_each_root_session_and_does_not_count_children_as_roo
|
|||
("SpanId".into(), json!(id)),
|
||||
("SpanName".into(), json!(name)),
|
||||
("ObservationType".into(), json!(kind)),
|
||||
("Framework".into(), json!(framework)),
|
||||
("Framework".into(), json!(if id == "legacy-sdk" { "claude-agent-sdk" } else { framework })),
|
||||
("AgentName".into(), json!("assistant")),
|
||||
("SpanAttributes".into(), json!({"session.id": session, "agent_id": actor, "query_source_safe": "sdk"})),
|
||||
("SpanAttributes".into(), json!({"session.id": session, "agent_id": actor, "query_source_safe": if id == "legacy-sdk" { "" } else { "sdk" }})),
|
||||
("TeamId".into(), json!("team-a")),
|
||||
("ApiKeyHash".into(), json!("key-a")),
|
||||
])).collect()).await?;
|
||||
|
|
@ -879,10 +893,10 @@ async fn native_list_counts_each_root_session_and_does_not_count_children_as_roo
|
|||
.get_trace(&store, &access, "mixed-sessions", &summary.trace_ref)
|
||||
.await?
|
||||
.ok_or("missing trace")?;
|
||||
assert_eq!((summary.agent_count, summary.agent_invocations), (3, 4));
|
||||
assert_eq!((summary.agent_count, summary.agent_invocations), (4, 6));
|
||||
assert_eq!(
|
||||
(detail.summary.agent_count, detail.summary.agent_invocations),
|
||||
(3, 4)
|
||||
(4, 6)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::query::named::TraceSpansRow;
|
||||
|
||||
use super::graph::Graph;
|
||||
|
||||
pub(super) fn native(row: &TraceSpansRow) -> bool {
|
||||
matches!(row.framework.as_str(), "claude-code" | "claude-agent-sdk")
|
||||
(row.framework == "claude-code"
|
||||
|| (row.framework == "claude-agent-sdk"
|
||||
&& (row.name.starts_with("claude_code.")
|
||||
|| !row.native_agent_id.is_empty()
|
||||
|| !row.query_source.is_empty())))
|
||||
&& (!row.session_id.is_empty() || !row.native_agent_id.is_empty())
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +21,15 @@ fn identity(row: &TraceSpansRow, agent: &str) -> String {
|
|||
} else {
|
||||
&row.session_id
|
||||
};
|
||||
format!("claude-code:{}:{session}:{agent}", session.len())
|
||||
let mut digest = Sha256::new();
|
||||
digest.update((session.len() as u64).to_be_bytes());
|
||||
digest.update(session);
|
||||
digest.update(agent);
|
||||
format!("claude-code:{:x}", digest.finalize())
|
||||
}
|
||||
|
||||
fn label(value: &str) -> String {
|
||||
value.chars().take(256).collect()
|
||||
}
|
||||
|
||||
fn root(row: &TraceSpansRow) -> String {
|
||||
|
|
@ -112,7 +126,7 @@ impl Actors {
|
|||
id: id.clone(),
|
||||
parent: (id != &root(row)).then(|| root(row)),
|
||||
name: if id == &root(row) {
|
||||
row.agent.clone()
|
||||
label(&row.agent)
|
||||
} else {
|
||||
"Subagent".to_owned()
|
||||
},
|
||||
|
|
@ -138,7 +152,7 @@ impl Actors {
|
|||
if let Some((_, name)) = name
|
||||
&& !name.is_empty()
|
||||
{
|
||||
actor.name = name.to_owned();
|
||||
actor.name = label(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1700,3 +1700,45 @@ fn unresolved_native_actors_are_distinguished_from_legacy_grouping(
|
|||
assert_eq!(trace.spans[0].actor_id, None);
|
||||
assert_eq!(trace.spans[0].actor_unassigned, unassigned);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn openinference_sdk_session_keeps_recorded_root_ownership() {
|
||||
let root = TraceSpansRow {
|
||||
framework: "claude-agent-sdk".into(),
|
||||
session_id: "sdk-session".into(),
|
||||
..row("root", "", "ClaudeAgentSDK.query", "agent", "sdk-reader")
|
||||
};
|
||||
let trace = resolve_trace("trace", "ref", &[root], &[]).unwrap();
|
||||
assert_eq!(trace.summary.agent_count, 1);
|
||||
assert_eq!(trace.agents[0].name, "sdk-reader");
|
||||
assert!(!trace.spans[0].actor_unassigned);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn inherited_native_actor_identity_and_labels_have_bounded_size() {
|
||||
let root = TraceSpansRow {
|
||||
framework: "claude-code".into(),
|
||||
session_id: "s".into(),
|
||||
native_agent_id: "a".repeat(65_536),
|
||||
query_source: format!("agent.custom.{}", "n".repeat(65_536)),
|
||||
..row("root", "", "claude_code.interaction", "agent", "assistant")
|
||||
};
|
||||
let children = (0..1_024).map(|index| TraceSpansRow {
|
||||
framework: "claude-code".into(),
|
||||
session_id: "s".into(),
|
||||
..row(
|
||||
&format!("child-{index}"),
|
||||
"root",
|
||||
"Read",
|
||||
"tool",
|
||||
"assistant",
|
||||
)
|
||||
});
|
||||
let rows: Vec<_> = std::iter::once(root).chain(children).collect();
|
||||
let trace = resolve_trace("trace", "ref", &rows, &[]).unwrap();
|
||||
assert_eq!(trace.summary.agent_count, 1);
|
||||
assert!(trace.spans.iter().all(|span| {
|
||||
span.actor_id.as_ref().is_some_and(|id| id.len() <= 80) && span.agent.len() <= 1_024
|
||||
}));
|
||||
assert!(serde_json::to_vec(&trace).unwrap().len() < 3_000_000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -616,41 +616,42 @@ describe("coding sessions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("keeps explicit native actors separate across workflow branches and background resumes", () => {
|
||||
const span = (id: string, actor: string, parentActor: string | null, offset: number): Span => ({
|
||||
...root,
|
||||
span_id: id,
|
||||
name: "claude_code.llm_request",
|
||||
type: "llm",
|
||||
framework: "claude-code",
|
||||
parent_span_id: "root",
|
||||
actor_id: actor,
|
||||
parent_actor_id: parentActor,
|
||||
agent: "general-purpose",
|
||||
start_offset_ms: offset,
|
||||
});
|
||||
const spans = [
|
||||
{ ...root, framework: "claude-code", actor_id: "root-actor", parent_actor_id: null, agent: "claude-code" },
|
||||
span("first", "actor-one", "root-actor", 1),
|
||||
span("second", "actor-two", "root-actor", 2),
|
||||
span("nested", "actor-three", "actor-one", 3),
|
||||
{ ...span("resumed", "actor-one", "root-actor", 4), parent_span_id: null },
|
||||
];
|
||||
const details = new Map(spans.map((span) => [span.span_id, detail(span.span_id, "", `Reply ${span.span_id}`)]));
|
||||
const items = buildConversation(spans, details, true);
|
||||
expect(items.filter((item) => item.span.span_id === "resumed").map((item) => item.agentId)).toEqual(["actor-one"]);
|
||||
const groups = groupConversation(items, spans);
|
||||
const branches = groups.filter((group) => group.kind === "branch");
|
||||
expect(branches.map((group) => group.id)).toEqual(["actor-one", "actor-two"]);
|
||||
expect(branches[0].children.map((group) => (group.kind === "branch" ? group.id : group.item.span.span_id))).toEqual([
|
||||
"first",
|
||||
"actor-three",
|
||||
"resumed",
|
||||
]);
|
||||
expect(
|
||||
items.filter((item) => ["first", "resumed"].includes(item.span.span_id)).map((item) => item.agentName),
|
||||
).toEqual(["general-purpose (1)", "general-purpose (1)"]);
|
||||
});
|
||||
it.each(["claude-code", "claude-agent-sdk"])(
|
||||
"keeps explicit %s actors separate across workflow branches and background resumes",
|
||||
(framework) => {
|
||||
const span = (id: string, actor: string, parentActor: string | null, offset: number): Span => ({
|
||||
...root,
|
||||
span_id: id,
|
||||
name: "claude_code.llm_request",
|
||||
type: "llm",
|
||||
framework,
|
||||
parent_span_id: "root",
|
||||
actor_id: actor,
|
||||
parent_actor_id: parentActor,
|
||||
agent: "general-purpose",
|
||||
start_offset_ms: offset,
|
||||
});
|
||||
const spans = [
|
||||
{ ...root, framework, actor_id: "root-actor", parent_actor_id: null, agent: "claude-code" },
|
||||
span("first", "actor-one", "root-actor", 1),
|
||||
span("second", "actor-two", "root-actor", 2),
|
||||
span("nested", "actor-three", "actor-one", 3),
|
||||
{ ...span("resumed", "actor-one", "root-actor", 4), parent_span_id: null },
|
||||
];
|
||||
const details = new Map(spans.map((span) => [span.span_id, detail(span.span_id, "", `Reply ${span.span_id}`)]));
|
||||
const items = buildConversation(spans, details, true);
|
||||
expect(items.filter((item) => item.span.span_id === "resumed").map((item) => item.agentId)).toEqual(["actor-one"]);
|
||||
const groups = groupConversation(items, spans);
|
||||
const branches = groups.filter((group) => group.kind === "branch");
|
||||
expect(branches.map((group) => group.id)).toEqual(["actor-one", "actor-two"]);
|
||||
expect(branches[0].children.map((group) => (group.kind === "branch" ? group.id : group.item.span.span_id))).toEqual(
|
||||
["first", "actor-three", "resumed"],
|
||||
);
|
||||
expect(
|
||||
items.filter((item) => ["first", "resumed"].includes(item.span.span_id)).map((item) => item.agentName),
|
||||
).toEqual(["general-purpose (1)", "general-purpose (1)"]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([false, true])("preserves legacy history while isolating unconfirmed actors (%s)", (unassigned) => {
|
||||
const spans: Span[] = [
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ function agentLabels(agents: readonly Span[], details: ReadonlyMap<string, SpanD
|
|||
const args = detail && isNativeAgent(agent) ? toolInput(detail.input, detail.input_ui) : undefined;
|
||||
const description = args && typeof args === "object" && "description" in args ? args.description : undefined;
|
||||
const name =
|
||||
agent.framework === "claude-code" && (agent.actor_id || !isNativeAgent(agent))
|
||||
agent.actor_id || (agent.framework === "claude-code" && !isNativeAgent(agent))
|
||||
? agent.agent || agent.name || "Agent"
|
||||
: agent.name || agent.agent || "Agent";
|
||||
return { agent, name: typeof description === "string" && description ? description : name };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue