fix: render plain string message content without extra quotes in Trace Details#11294
fix: render plain string message content without extra quotes in Trace Details#11294RogerHYang merged 1 commit intomainfrom
Conversation
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
5cea572 to
48935f6
Compare
48935f6 to
9aa3485
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
…e Details Root cause: #10941 fixed the tool-return React error by running all message content in SpanDetails through formatContentAsString(). That function was moved from playgroundUtils (where it was only used for tool content) and was designed to JSON.stringify plain strings. Using it for every message in the trace UI made system/user/model text show with extra quotes and escaped newlines. Fix: return plain string content as-is from formatContentAsString when it is not double-stringified JSON or a non-string JSON value. Tool results (objects/arrays) still get pretty-printed via JSON.stringify. Other call sites (playground, messageSchemas, ChatTemplateMessageCard) only pass tool content, so behavior there is unchanged.
9aa3485 to
0c0fd74
Compare
Full history of
|
| From commit | To commit | Usage |
|---|---|---|
| e97155905 (2024-11-19) | 7f4d6bdd (#6084) | 1. MessageEditor: for message.role === "tool", toolMessageContent = normalizeMessageAttributeValue(message.content) passed as value={toolMessageContent} to JSONEditor (so the tool message content is normalized before showing in the editor). 2. SortableMessageItem: CopyToClipboardButton text={...} when not toolCalls used normalizeMessageAttributeValue(message.content) (so copied text is normalized). |
| 7f4d6bdd (#6084, 2025-01-16) | 7fb4ef6cd (#6454) | 1. MessageEditor for tool role was changed to message.content || "" (no normalization). 2. Copy button still used normalizeMessageContent(message.content). |
| 7fb4ef6cd (#6454, 2025-02-19) | 7ff1dbc84 (#6914) | 1. MessageEditor for tool role was changed back to toolMessageContent = normalizeMessageContent(message.content) for the JSONEditor value. 2. Copy still used normalizeMessageContent(message.content). |
| 7ff1dbc84 (#6914, 2025-03-27) | present | No usage. Both uses removed: MessageEditor uses value={message.content ?? '""'}, Copy uses (message.content ?? ""). So this file no longer imports or calls the function. |
app/src/pages/playground/playgroundUtils.ts
| From commit | To commit | Usage |
|---|---|---|
| 7ff1dbc84 (#6914, 2025-03-27) | present | Internal: In transformSpanAttributesToPlaygroundInstance, when building messages from getTemplateMessagesFromAttributes result: messages = rawMessages?.map((message) => ({ ...message, content: message.role === "tool" ? normalizeMessageContent(message.content) : message.content })). So when loading a span into the playground, tool-role message content is normalized. After #10941 the call is formatContentAsString(message.content) (import from jsonUtils). |
app/src/schemas/messageSchemas.ts
| From commit | To commit | Usage |
|---|---|---|
| 4a74ff1b5 (#6132, 2025-01-21) | present | In promptMessageToOpenAI transform: when prompt.role === "TOOL", the OpenAI-form message's content is set to normalizeMessageContent(toolResult.toolResult.result) (so tool result is formatted to a string for the API). After #10941 the call is formatContentAsString(toolResult.toolResult.result) (import from jsonUtils). |
app/src/pages/prompt/ChatTemplateMessageCard.tsx
| From commit | To commit | Usage |
|---|---|---|
| 7f4d6bdd (#6084, 2025-01-16) | present | In ChatTemplateMessageToolResultPart: value = useMemo(() => normalizeMessageContent(convertedToolResult), [toolResult]) (or formatContentAsString(convertedToolResult) after #10941). That value is used for the displayed tool result content in the prompt template message card. |
app/src/pages/trace/SpanDetails.tsx
| From commit | To commit | Usage |
|---|---|---|
| 3ce4ca80a (#10941, 2026-01-21) | 0c0fd74c2 | 1. In LLMMessage: normalizedContent = formatContentAsString(messageContent) (no options), passed as children to ConnectedMarkdownBlock. 2. In MessageContentListItem (multi-modal message_content): normalizedText = text ? formatContentAsString(text) : undefined, then passed to ConnectedMarkdownBlock. Both call sites added in #10941; no options. |
| 0c0fd74c2 (2026-02-08) | present | Same two call sites, now both with { unquotePlainString: true }: 1. LLMMessage: formatContentAsString(messageContent, { unquotePlainString: true }). 2. MessageContentListItem: formatContentAsString(text, { unquotePlainString: true }). |
Tests
| From commit | To commit | Usage |
|---|---|---|
| (various) | 3ce4ca80a (#10941) | normalizeMessageContent was tested in app/src/pages/playground/__tests__/playgroundUtils.test.ts. |
| 3ce4ca80a (#10941) | present | Tests moved to app/src/utils/__tests__/jsonUtils.test.ts as formatContentAsString tests (strings, numbers, booleans, null, arrays, objects, double-quoted strings, undefined, unquotePlainString). |
Summary timeline
| Date | Commit | Name (at time) | Location | Main change |
|---|---|---|---|---|
| 2024-11-19 | e97155905 | normalizeMessageAttributeValue | playgroundUtils | Introduced; string as-is, object pretty-printed; null/empty → "{}". |
| 2025-01-16 | 3acd71299 | normalizeMessageContent | playgroundUtils | Rename; accept unknown; add double-stringified parse-twice handling. (#6084) |
| 2025-03-27 | 7ff1dbc84 | normalizeMessageContent | playgroundUtils | Remove empty/undefined→"{}"; use when mapping span→playground tool messages. (#6914) |
| 2026-01-21 | 3ce4ca80a | formatContentAsString | jsonUtils | Move + rename; add nonStringStart; use in SpanDetails for #10921. (#10941) |
| 2026-02-08 | 0c0fd74c2 | formatContentAsString | jsonUtils | Add unquotePlainString; remove nonStringStart; fix plain-string display in Trace. |
Current call sites
app/src/pages/trace/SpanDetails.tsx– Normalize message content for LLM messages (withunquotePlainString: truefor display); format tool/other text for display.app/src/pages/playground/playgroundUtils.ts– When transforming span attributes to playground instance, normalize tool message content.app/src/schemas/messageSchemas.ts– When converting prompt message to OpenAI format, format tool result content.app/src/pages/prompt/ChatTemplateMessageCard.tsx– Format converted tool result for display.- Tests:
app/src/utils/__tests__/jsonUtils.test.ts–formatContentAsStringtests (including double-quoted string and unquotePlainString).
Notes
- Double-stringified handling (parse twice for
"{,"[,"\") was introduced in feat(prompts): Save and display playground instances as multi-part content prompts #6084 for playground/prompts; it was not described in the trace UI bug [BUG] Tool return is not rendered in the phoenix UI #10921. It was carried over when the same utility was reused and moved for fix: normalize tool return content before rendering #10941. - The function's behavior for
undefinedand empty string has varied: originally both returned"{}"; after fix: Remove unpredictable playground transformations #6914 they fell through (with a possibleundefinedreturn forundefined); after the 0c0fd74c2 cleanup,undefinedis explicitly turned into the string"undefined"viaString(content).
…e Details (Arize-ai#11294) Root cause: Arize-ai#10941 fixed the tool-return React error by running all message content in SpanDetails through formatContentAsString(). That function was moved from playgroundUtils (where it was only used for tool content) and was designed to JSON.stringify plain strings. Using it for every message in the trace UI made system/user/model text show with extra quotes and escaped newlines. Fix: return plain string content as-is from formatContentAsString when it is not double-stringified JSON or a non-string JSON value. Tool results (objects/arrays) still get pretty-printed via JSON.stringify. Other call sites (playground, messageSchemas, ChatTemplateMessageCard) only pass tool content, so behavior there is unchanged.
Root cause: #10941 fixed the tool-return React error by running all message content in SpanDetails through formatContentAsString(). That function was moved from playgroundUtils (where it was only used for tool content) and was designed to JSON.stringify plain strings. Using it for every message in the trace UI made system/user/model text show with extra quotes and escaped newlines.
Fix: return plain string content as-is from formatContentAsString when it is not double-stringified JSON or a non-string JSON value. Tool results (objects/arrays) still get pretty-printed via JSON.stringify. Other call sites (playground, messageSchemas, ChatTemplateMessageCard) only pass tool content, so behavior there is unchanged.
Before
After
Note
Low Risk
UI-only formatting changes to how message content is stringified/rendered; low risk aside from potential edge-case display differences for string-like JSON inputs.
Overview
Trace span details now render plain text LLM message content without extra JSON quotes by calling
formatContentAsStringwithunquotePlainString: truefor message bodies and multi-modal text items.formatContentAsStringis extended with an optionalunquotePlainStringflag, updated to keep double-stringified JSON pretty-printing while making string vs non-string handling more explicit (including a defined fallback forundefined), and tests are updated to cover theundefinedcase.Written by Cursor Bugbot for commit 0c0fd74. This will update automatically on new commits. Configure here.