fix(client): apply mustache substitution to text items in array message content#801
Conversation
|
@Ashut0sh-mishra is attempting to deploy a commit to the langfuse Team on Vercel. A member of the Team first needs to authorize it. |
| makeChatPrompt([ | ||
| { type: ChatMessageType.ChatMessage, role: "system", content: "You are helpful." }, | ||
| { type: ChatMessageType.Placeholder, name: "attachments" }, | ||
| ]), | ||
| ); | ||
| const attachments = [ | ||
| { | ||
| role: "user", | ||
| content: [ | ||
| { type: "input_file", file_url: "path_to_pdf" }, | ||
| ], | ||
| }, | ||
| ]; | ||
| const result = client.compile({ emailBody: "test" }, { attachments }) as any[]; | ||
| expect(result).toHaveLength(2); | ||
| expect(result[1].role).toBe("user"); | ||
| expect(result[1].content[0]).toEqual({ type: "input_file", file_url: "path_to_pdf" }); |
There was a problem hiding this comment.
No test for mustache substitution in placeholder-injected array content
The test verifies that array content from a placeholder passes through without crashing, but the injected messages only contain an input_file part (which is intentionally not substituted). There is no test that injects a placeholder whose messages contain a { type: "text", text: "Hello {{name}}" } part and then asserts that compile({ name: "Alice" }, { ... }) resolves it to "Hello Alice". Given that variable substitution in placeholder array content is now supported (the injected messages go through the map and hit the new array branch), a test for this path would confirm the intended behaviour and guard against regressions.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/client/src/prompt/promptClients.test.ts
Line: 84-100
Comment:
**No test for mustache substitution in placeholder-injected array content**
The test verifies that array content from a placeholder passes through without crashing, but the injected messages only contain an `input_file` part (which is intentionally not substituted). There is no test that injects a placeholder whose messages contain a `{ type: "text", text: "Hello {{name}}" }` part and then asserts that `compile({ name: "Alice" }, { ... })` resolves it to `"Hello Alice"`. Given that variable substitution in placeholder array content is now supported (the injected messages go through the `map` and hit the new array branch), a test for this path would confirm the intended behaviour and guard against regressions.
How can I resolve this? If you propose a fix, please make it concise.
…ge content
ChatPromptClient.compile() silently skipped variable substitution when a
message's content was an array (multimodal format). Text-type content parts
containing {{variable}} placeholders were returned unchanged instead of
having variables resolved.
Fix by iterating array content parts in compile() and applying
mustache.render() to items where type === 'text' and the 'text' field is
a string, while leaving image_url, input_file, and all other content part
types untouched. Apply the same pattern in getLangchainPrompt() where
_transformToLangchainVariables() was called directly on content that could
be an array.
Add unit tests covering string content regression, crash prevention,
variable substitution in text parts, and placeholder resolution with
multimodal content.
Fixes langfuse/langfuse#12338
Co-authored-by: nik464 <nik464@users.noreply.github.com>
…d array content Addresses greptile review on langfuse#801: verify that `{{variable}}` placeholders in text parts of messages injected via a placeholder are resolved during `compile()`. Also asserts non-text parts (e.g. `image_url`) within placeholder content are passed through untouched. Co-authored-by: nik464 <nik464@users.noreply.github.com>
b7d4cd7 to
241b574
Compare
|
Thanks @greptile-apps! Added the missing test in the latest commit. It injects a placeholder whose messages contain a text part with a {{name}} placeholder plus an image_url part, then asserts: (1) the text part is substituted (Hello Alice), and (2) the non-text image_url inside placeholder content is passed through untouched, matching the documented intent. Also rebased on main so the branch is no longer behind. |
|
The new test (lines 101–129) covers exactly the missing path — placeholder injection with a mixed The only remaining open item from the original review is Issue 1 — the three independent copies of the part-mapping pattern in Tip: You can customize Greptile's behavior for this repo with |
Problem
ChatPromptClient.compile() silently skips variable substitution when a chat message's content field is an array (multimodal / OpenAI-style format, e.g. [{ type: 'text', text: 'Hello {{name}}' }, { type: 'image_url', ... }]).
The existing guard ypeof item.content === 'string' short-circuits to the \else\ branch for array content, returning the array completely unchanged — {{variable}}\ placeholders inside text parts are never resolved.
Reported in langfuse/langfuse#12338.
Root cause
ypescript // Before — array content falls into the else branch untouched if (... && typeof item.content === 'string') { return { ...item, content: mustache.render(item.content, variables) }; } else { return item; // ← arrays pass through here with no substitution }Fix
Split the \string\ branch into two cases:
The same two-case pattern is applied inside getLangchainPrompt() wherever _transformToLangchainVariables() was previously called directly on msg.content/item.content (which would throw on arrays).
Tests
Added packages/client/src/prompt/promptClients.test.ts with cases covering:
Checklist
Greptile Summary
This PR fixes
ChatPromptClient.compile()andgetLangchainPrompt()silently skipping Mustache variable substitution when a chat message'scontentfield is an array (multimodal / OpenAI-style format). The fix adds anArray.isArraybranch that iterates content parts and appliesmustache.render(or_transformToLangchainVariables) only to parts withtype === "text", leaving all other part types untouched.compile()fix: a newelse if (Array.isArray(item.content))branch in the finalmaphandles multimodal messages; placeholder-injected messages with array content also flow through this samemapand benefit automatically.getLangchainPrompt()fix: two symmetric array-content branches are added—one for placeholder-filled messages and one for regular chat messages—both converting{{var}}syntax to{var}for LangChain.vitestcases cover string-content regression, no-crash on array content, text-part substitution, non-text part pass-through, and placeholder resolution with array content.Confidence Score: 4/5
Safe to merge; the fix correctly extends both compile() and getLangchainPrompt() to handle array content, and existing string-content behaviour is unchanged.
The core logic is sound across all three new code paths and the added tests cover the key scenarios. The two remaining gaps—duplicated part-mapping code and a missing test for mustache substitution inside placeholder-injected array content—are style and coverage concerns that do not affect correctness of the shipped fix.
promptClients.ts warrants a second look for the three independent copies of the part-mapping pattern, which would be worth consolidating before the codebase grows further.
Sequence Diagram
sequenceDiagram participant Caller participant compile participant map as compile() map participant mustache Caller->>compile: compile(variables, placeholders) Note over compile: Resolve placeholders into messagesWithPlaceholdersReplaced compile->>map: map over resolved messages alt string content map->>mustache: mustache.render(content, variables) mustache-->>map: rendered string else array content (NEW) loop each part in content array alt "part.type === text" map->>mustache: mustache.render(part.text, variables) mustache-->>map: rendered text else non-text part map->>map: pass through unchanged end end else other or placeholder map->>map: return item as-is end map-->>Caller: compiled messages[]Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix(client): apply mustache substitution..." | Re-trigger Greptile