Skip to content

Landing v2 - #30

Merged
Makisuo merged 8 commits into
mainfrom
landing-v2
May 3, 2026
Merged

Makisuo merged 8 commits into
mainfrom
landing-v2

Conversation

@Makisuo

@Makisuo Makisuo commented May 3, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

Makisuo and others added 8 commits May 3, 2026 01:24
Replace the tabbed SDK showcase with a vertical 01/02/03 stepper.
Each step has an amber-bordered numeric badge with a glyph (grid /
download / brackets), and the rail carries a subtle amber connector
spine on desktop. SDK chips show brand-colored inline logos
(Node.js, Next.js, Python, Go, Effect, OTel). The endpoint URL
gets a pulsing dot, and the instrument header shows the active
file's logo plus filename. Code blocks are now syntax-highlighted
via Astro's <Code> component (vitesse-dark theme), with the install
command rendered as a shell prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Makisuo
Makisuo merged commit 285808f into main May 3, 2026
1 of 3 checks passed
JeremyFunk added a commit that referenced this pull request Sep 28, 2026
…ys (#30)

Follow-up to the chat.completion decoding. Every Python OpenInference
instrumentor writes model messages only as flattened keys
(`llm.input_messages.N.message.role`, `….contents.M.message_content.text`,
`….tool_calls.M.tool_call.function.name`, `….tool_call_id`); the
unflattened `llm.input_messages` / `llm.output_messages` keys the
integration listed never appear. Without the GenAI dual-write, dspy,
smolagents and llamaindex model calls fell back to `input.value`, a Python
repr (`ChatMessage(role=<MessageRole.USER: 'user'>, ...)`), and agno and
crewai sessions had no transcript and no turn labels at all.

The span read now also projects key prefixes an integration declares
(`refinePrefixes`, beside the existing prompt-variable family), and the
OpenInference refine rebuilds the flattened families as OpenAI chat
messages. Preference per message field: the GenAI dual-write, then
`input.value` / `output.value` when it is a message list (the exact
capture), then the flattened keys, then the bare value on a model call.

With tool calls now decoded from OpenInference outputs, a call whose tool
span recorded no call id (OpenInference never stamps one) rendered twice:
from the span and from the message. The transcript's name fallback for
unmatched `tool_call` parts now also covers parts whose id no tool span
carries.

Seen in: dspy, smolagents, llamaindex, agno, crewai, langchain
(docs_* captures, with and without the GenAI dual-write), openai-agents TS.
JeremyFunk added a commit that referenced this pull request Sep 28, 2026
…#30)

Review follow-up: a chat.completion whose choices hold no `message` (a
streamed chunk's `delta`, an empty `choices`) was unwrapped to an empty
list, which erased the captured output. The capture is now left as it
was unless the choices yield at least one message.
JeremyFunk added a commit that referenced this pull request Sep 28, 2026
Follow-up to the tool-row dedupe. Once an OpenInference tool call renders
once, from its span, a failed span that recorded no result attribute and
no call id lost the only error text the session had (the history echo
under the message's call id): crewai_b, langchain_b and llamaindex_b
rendered result "not captured ... Whether it succeeded is unknown" for a
call that failed with "transport data service unavailable (503)".

A failed tool span with no captured or echoed result now falls back to
its failure text (`rawFailureText`: the status message unless generic).
The transcript never tells a reader a failed call's outcome is unknown:
when even that is absent the note says the span failed without recording
a result or an error message.
JeremyFunk added a commit that referenced this pull request Sep 28, 2026
llamaindex writes `llm.output_messages.N.message.contents.0.message_content.text = ""`
beside its tool calls. The empty value is skipped, which left a bare
`{type: "text"}` part that rendered as raw assistant text (7 rows in
docs_llamaindex_a/b without the GenAI dual-write). A content part carrying
nothing but its type is now dropped.
JeremyFunk added a commit that referenced this pull request Sep 29, 2026
* fix(agent-sessions): keep plain-text values of JSON-typed GenAI attributes

Symptom: tool results that are plain strings or numbers (OpenAI Agents
`deleted /tmp/scratch-notes.txt`, `391`, a 503 error text, a handoff
summary) read "result: not captured", and Mastra's plain-text
`gen_ai.system_instructions` never reached the transcript.

Cause: the mapper decoded every `json` catalog field with JSON.parse and
kept only objects and arrays, so anything else was dropped. The
convention types `gen_ai.tool.call.arguments` / `.result` as any value.

Fix: JSON null still means not captured; objects, arrays and JSON strings
decode as before; everything else (plain text, numbers, booleans,
truncated JSON) is kept as the text it arrived as.

Seen in: openai-agents (docs_openai-agents_a, spans bf977b7555424c18,
bda436b865ce7528, 189ec7a1efd8b75b) and mastra (docs_mastra_a).

* fix(agent-sessions): read OpenAI-format tool_calls and tool messages

Symptom: a model call whose reply only calls a tool showed no output row
and no tool-call request (LiteLLM a1 turn-3 `chat` span 7072bdfe10b317a7);
in the proxy capture the assistant tool-call message was missing from the
input history (6 of 7 messages shown). OpenInference dual-write histories
lose the same messages.

Cause: `messageParts` (span-detail.ts) read only `parts` / `content`, so an
OpenAI chat message with `content: null` and a `tool_calls` array had no
parts and was skipped.

Fix: read `tool_calls` (`{ id, function: { name, arguments } }`) as
tool_call parts beside the content, and a `{ role: "tool", tool_call_id }`
message as the tool_result for that call id, which also lets the session
result index match it.

Seen in: litellm (docs_litellm_a, docs_litellm_proxy), OpenInference TS
(docs_openai-agents_ts input.value).

* fix(agent-sessions): unwrap the OpenRouter Broadcast message envelopes

Symptom: every OpenRouter Broadcast `LLM Generation` span showed two raw
JSON messages (span a04a15db8c490dbb: input/user = the whole
`{"messages":[...]}` blob, output/assistant = the `{"completion":"",
"reasoning":...}` blob); the 16 turns read "Segment 1..16" with no turn
label and the session had no title.

Cause: Broadcast sends `gen_ai.prompt` as `{ messages }` and
`gen_ai.completion` as `{ completion, reasoning, rawRequest }`. The mapper
passed both through, and every reader (turn label, transcript, span
detail) expects the documented message array.

Fix: the default integration's refine unwraps a `{ messages: [...] }`
envelope on either message field, and turns `{ completion, reasoning }`
into one assistant message with a reasoning part and a text part. Shape
keyed, so OpenInference's `input.value` request body benefits too.

Seen in: openrouter (capture `openrouter`, 1595 prompts / 1594
completions in these shapes).

* fix(agent-sessions): decode chat.completion output and llm.finish_reason

Symptom: no model reply and no tool-call request on any OpenInference TS
model span (docs_openai-agents_ts generations fa84aae0ec2fa65d,
9e250b5c18100b86), so the last reply of each turn was absent; the
reply-length check was skipped because no finish reason was read.

Cause: the OpenInference integration reads `llm.output_messages`, a key
OpenInference never writes (it flattens to `llm.output_messages.N.*`),
then `output.value`, which OpenInference TS sets to the raw
`[{"object":"chat.completion","choices":[...]}]`; the message walker
skips entries without role/parts/content. `llm.finish_reason` was not
mapped.

Fix: the default refine turns a `chat.completion` object (alone or in an
array) into its choices' messages, carrying each choice's finish reason
onto the message; the OpenInference integration reads
`llm.finish_reason` as the response finish reason. The flattened keys are
left unread: `output.value` carries the same reply, and reading them would
need a prefix projection on the span read.

Seen in: openai-agents TS (docs_openai-agents_ts, vendor
unknown:openinference); OpenRouter's test generation uses the same shape.

* fix(agent-sessions): read finish reasons off the output messages

Symptom: the reply-length check was always skipped for Strands sessions
("No model call recorded a finish reason"), and call meta lines showed no
stop reason, although every `chat` span recorded one.

Cause: the mapper reads finish reasons only from
`gen_ai.response.finish_reasons` (and aliases). Strands (Python and TS)
writes them only where the convention also allows them, as
`finish_reason` on each `gen_ai.output.messages` entry.

Fix: when the span carries no finish reason attribute, the default refine
collects the non-empty `finish_reason` values of its output messages. The
attribute still wins when present; a streamed chunk's empty reason is
ignored. This also picks up the reasons the chat.completion unwrap
carries onto each message.

Seen in: strands (docs_strands_a, docs_strands_g, docs_strands_ts).

* fix(agent-sessions): read a streamed Google ADK reply as its aggregate

Symptom: a streamed Google ADK turn rendered its reply as 8 assistant
messages, 7 one-token chunks followed by the full text (trace
1feecb5b2b06551a5c1ce516083635cc, span 32be028f453b9eb6).

Cause: ADK writes every streamed chunk into `gen_ai.output.messages` with
an empty `finish_reason`, then the aggregate with the real one, and the
message walker rendered each entry.

Fix: output messages whose `finish_reason` is the empty string are
dropped when a finished message is present to stand for them. Chunks with
no aggregate behind them are kept.

Seen in: google-adk (docs_google-adk_a, `generate_content` span
8f487cfcae3893f5 in the capture).

* fix(agent-sessions): unwrap the LangChain ToolMessage tool result

Symptom: every LangChain tool result rendered as the serialised
ToolMessage (`{"type": "tool", "data": {"content": "391",
"additional_kwargs": {}, ..., "status": "success"}}`) instead of what the
tool returned.

Cause: OpenInference's LangChain instrumentor dual-writes the whole
ToolMessage into `gen_ai.tool.call.result`, and the mapper passed it
through.

Fix: the default refine replaces a result of exactly that shape
(`type: "tool"` with a `data` object of `type: "tool"` carrying
`content`) by `data.content`. Any other object stays whole.

Seen in: langchain (docs_langchain_a, docs_langchain_b: every TOOL span).

* fix(agent-sessions): decode OpenInference keys for framework vendor ids

Symptom: the session detail page showed no messages, usage or cost for
spans the gateway stamps as dspy, agno, smolagents, openai_agents_sdk or
crewai unless the app turned on OpenInference's GenAI dual-write
(`TraceConfig(enable_genai_semconv=True)`, the workaround in all five
guides). The list read the same spans' `llm.*` keys, so list and detail
disagreed (agno: list $0.0042, detail none).

Cause: the OpenInference integration was registered only under
`openinference-openai` and `unknown:openinference`
(ai-vendors.ts AI_VENDOR_INTEGRATIONS). The gateway stamps the framework
id for `openinference.instrumentation.<framework>` scopes, and those ids
fell through to the default integration, which reads only `gen_ai.*`.

Fix: register the OpenInference integration under agno, crewai, dspy,
openai_agents_sdk and smolagents, plus langchain and llamaindex, whose
OpenInference scopes the gateway may fingerprint as those ids. Their
native spans carry no OpenInference keys, and the canonical `gen_ai.*`
keys keep priority, so nothing a native span decodes changes.

Seen in: agno, crewai, dspy, openai-agents, smolagents (docs_* captures).

* fix(agent-sessions): prefer real tool arguments over a dual-written schema

Symptom: crewai and llamaindex tool spans showed the tool's JSON schema
(`{"properties": {"city": {...}}, "required": ["city"], "type":
"object"}`) as the call's arguments instead of `{"city": "Berlin"}`.

Cause: OpenInference's GenAI dual-write (`enable_genai_semconv`) copies
`tool.parameters` into `gen_ai.tool.call.arguments` for these
instrumentors (an upstream bug), and the canonical key wins over the
dialect's keys.

Fix: the OpenInference integration's refine replaces
`gen_ai.tool.call.arguments` by `input.value` when its raw value is
exactly the span's `tool.parameters`. Exact equality, so arguments that
merely resemble a schema are never touched. The refine context gains
`read(field, key)`, the mapper's own decoder, so a refine can read another
key the way the source lists do.

Seen in: crewai (docs_crewai_a `get_weather.run` 0a327721d936828a),
llamaindex (docs_llamaindex_a `FunctionTool.acall` 8dc64cacfbc534dc).

* fix(agent-sessions): read a bare OpenAI message output as the reply (#14)

Follow-up to the OpenAI tool_calls fix. smolagents' model spans write
`output.value` as one OpenAI message object (`{role, content: null,
tool_calls: [...]}`), not an array, so the walker rendered it as raw JSON
and the tool-call request was lost.

The default refine now wraps a single record carrying a `role` into a
one-element message array; the tool_calls reading then applies.

Seen in: smolagents (docs_smolagents_a `OpenAIModel.generate`
db63f17aa5554acf).

* fix(agent-sessions): read OpenInference input/output values by span kind (#3)

Follow-up to registering the OpenInference integration for framework
vendor ids. Once smolagents and agno spans decoded `input.value` /
`output.value` as message fields, an agent run's arguments became the
turn's user row: smolagents' `CodeAgent.run` anchor rendered
`{"task": "Hi! Briefly introduce yourself.", "stream": false, ...}` as the
user message, and agno's plain-text agent input came before the system
row and suppressed the model call's own user row.

`input.value` and `output.value` are whatever the span's function took and
returned. They leave the source lists and are read in the refine: on a
model call (or a span naming no kind) they are the request and reply as
before; on any other kind they are messages only when they unwrap to a
role-carrying message list (LangChain's `{messages}` chain input); on a
TOOL span they fill the call's arguments and result when the GenAI
attributes are absent. The schema-as-arguments replacement moves under
the TOOL branch.

The envelope normalisers move to `ai-messages.ts` so the vendor refine can
unwrap the same way the default one does. The integration's doc comment
no longer claims the gateway already stamps langchain / llamaindex for
OpenInference scopes; that takes effect with the ingest detection batch.

Seen in: smolagents (docs_smolagents_a/b `assistant.run` a92540159081b72d),
agno (docs_agno_a).

* fix(agent-sessions): read OpenInference's flattened llm.*_messages keys (#30)

Follow-up to the chat.completion decoding. Every Python OpenInference
instrumentor writes model messages only as flattened keys
(`llm.input_messages.N.message.role`, `….contents.M.message_content.text`,
`….tool_calls.M.tool_call.function.name`, `….tool_call_id`); the
unflattened `llm.input_messages` / `llm.output_messages` keys the
integration listed never appear. Without the GenAI dual-write, dspy,
smolagents and llamaindex model calls fell back to `input.value`, a Python
repr (`ChatMessage(role=<MessageRole.USER: 'user'>, ...)`), and agno and
crewai sessions had no transcript and no turn labels at all.

The span read now also projects key prefixes an integration declares
(`refinePrefixes`, beside the existing prompt-variable family), and the
OpenInference refine rebuilds the flattened families as OpenAI chat
messages. Preference per message field: the GenAI dual-write, then
`input.value` / `output.value` when it is a message list (the exact
capture), then the flattened keys, then the bare value on a model call.

With tool calls now decoded from OpenInference outputs, a call whose tool
span recorded no call id (OpenInference never stamps one) rendered twice:
from the span and from the message. The transcript's name fallback for
unmatched `tool_call` parts now also covers parts whose id no tool span
carries.

Seen in: dspy, smolagents, llamaindex, agno, crewai, langchain
(docs_* captures, with and without the GenAI dual-write), openai-agents TS.

* test(agent-sessions): refresh the integration SQL baseline for the span projection

The session span read now projects llm.finish_reason, tool.parameters and
the OpenInference llm.*_messages prefixes; the recorded SQL baseline
follows.

* fix(agent-sessions): drop streamed chunks only beside a finished aggregate (#20)

Review follow-up: the chunk filter dropped empty-finish-reason messages
whenever any other message was present, including one that names no
finish reason at all, so an unfinished stream beside an ordinary message
lost its chunks. Chunks are now dropped only when a message with a
non-empty finish reason is there to stand for them.

* fix(agent-sessions): keep a completion whose choices carry no message (#30)

Review follow-up: a chat.completion whose choices hold no `message` (a
streamed chunk's `delta`, an empty `choices`) was unwrapped to an empty
list, which erased the captured output. The capture is now left as it
was unless the choices yield at least one message.

* fix(agent-sessions): show a failed tool span's error as its result (#30)

Follow-up to the tool-row dedupe. Once an OpenInference tool call renders
once, from its span, a failed span that recorded no result attribute and
no call id lost the only error text the session had (the history echo
under the message's call id): crewai_b, langchain_b and llamaindex_b
rendered result "not captured ... Whether it succeeded is unknown" for a
call that failed with "transport data service unavailable (503)".

A failed tool span with no captured or echoed result now falls back to
its failure text (`rawFailureText`: the status message unless generic).
The transcript never tells a reader a failed call's outcome is unknown:
when even that is absent the note says the span failed without recording
a result or an error message.

* fix(agent-sessions): drop empty flattened content parts (#30)

llamaindex writes `llm.output_messages.N.message.contents.0.message_content.text = ""`
beside its tool calls. The empty value is skipped, which left a bare
`{type: "text"}` part that rendered as raw assistant text (7 rows in
docs_llamaindex_a/b without the GenAI dual-write). A content part carrying
nothing but its type is now dropped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant