This repository was archived by the owner on Jun 19, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
fix(openai): Uint8Array body + shrink few-shot to 1 example (Devvit 400 round 5) #36
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1293,27 +1293,33 @@ async function callOpenAI( | |
| // model still learns the rule-compilation pattern. Length caps preserved | ||
| // (rule ≤ 1000, clarification ≤ 500) for prompt-injection surface control. | ||
| const clarif = clarificationAnswer?.trim().slice(0, 500); | ||
| // Flatten to a single line of ASCII without `\n` so the wire body has zero | ||
| // newline-escape sequences. PR #34 dropped reasoning_effort + verbosity but | ||
| // production v0.0.35 still hit 400; the only remaining variable separating | ||
| // our body from probe(f) (the 6 KB single-user 200 OK shape) is escape-char | ||
| // density (`\n` and `\"`). Collapsing whitespace eliminates the `\n` family | ||
| // entirely. The few-shot OUTPUT examples still produce `\"` from the outer | ||
| // JSON.stringify wrapping our content string -- if 400 persists after this, | ||
| // we'll need to express few-shot output without quoted keys. | ||
| // Round-5 strategy: ship a minimal-shape body that exactly matches probe(f) | ||
| // (the only production-verified 200-OK shape at ~6 KB): | ||
| // - single user message | ||
| // - no `\n` in content (use ' ' between sections) | ||
| // - few-shot truncated to 1 example so total body fits well under 5610 B | ||
| // (probe(f)'s exact size). PR #32-#35 all kept 4 examples; 400 persisted. | ||
| // - {model, messages, max_completion_tokens, response_format} only | ||
| // Plus: send the body as a Uint8Array rather than a string, so Devvit's | ||
| // HTTP plugin transports raw bytes without re-encoding the JSON wrapper. | ||
| const collapse = (s: string) => s.replace(/\s+/g, ' ').trim(); | ||
| const exampleLine = (ex: (typeof FEW_SHOT_EXAMPLES)[number], i: number) => | ||
| `EXAMPLE ${i + 1} INPUT: ${collapse(ex.user)} OUTPUT: ${JSON.stringify(ex.assistant)}`; | ||
| const SHORT_PROMPT_CHARS = 3500; // hard cap on system prompt; rest of body stays small | ||
| const systemShort = collapse(VIBE_MOD_SYSTEM_PROMPT).slice(0, SHORT_PROMPT_CHARS); | ||
| const firstExample = FEW_SHOT_EXAMPLES[0]; | ||
| const exampleText = firstExample | ||
| ? `EXAMPLE INPUT: ${collapse(firstExample.user)} EXAMPLE OUTPUT: ${JSON.stringify(firstExample.assistant)}` | ||
| : ''; | ||
| const compositeContent = [ | ||
| 'SYSTEM INSTRUCTIONS:', | ||
| collapse(VIBE_MOD_SYSTEM_PROMPT), | ||
| 'EXAMPLES:', | ||
| ...FEW_SHOT_EXAMPLES.map(exampleLine), | ||
| systemShort, | ||
| exampleText, | ||
| 'TASK INPUT:', | ||
| collapse(userRule.slice(0, 1000)), | ||
| ...(clarif ? ['TASK CLARIFICATION:', collapse(clarif)] : []), | ||
| 'OUTPUT (strict JSON only, no prose):', | ||
| ].join(' '); | ||
| ] | ||
| .filter(Boolean) | ||
| .join(' '); | ||
| const messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }> = [ | ||
| { role: 'user', content: compositeContent }, | ||
| ]; | ||
|
|
@@ -1345,10 +1351,23 @@ async function callOpenAI( | |
| // body as pure 7-bit ASCII bytes, sidestepping the UTF-8-on-the-wire bug. | ||
| const asciiSafeBody = rawBody.replace(/[-]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')); | ||
|
|
||
| // Round 5: send body as Uint8Array with explicit Content-Length. String | ||
| // bodies travel through Devvit's HTTP plugin as a JS string that the plugin | ||
| // re-encodes to UTF-8 before the wire write; PR #32-#35 history shows | ||
| // large stringified-JSON bodies corrupt somewhere in that re-encode path. | ||
| // Uint8Array skips it: bytes are already final, plugin just streams them. | ||
| // Body is already pure ASCII so TextEncoder().encode produces exactly | ||
| // asciiSafeBody.length bytes (one byte per char). | ||
| const bodyBytes = new TextEncoder().encode(asciiSafeBody); | ||
| console.log('[vibe-mod] callOpenAI: body bytes =', bodyBytes.byteLength); | ||
| const resp = await fetch('https://api.openai.com/v1/chat/completions', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` }, | ||
| body: asciiSafeBody, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Content-Length': String(bodyBytes.byteLength), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manually setting the |
||
| Authorization: `Bearer ${apiKey}`, | ||
| }, | ||
| body: bodyBytes, | ||
| }); | ||
|
|
||
| if (!resp.ok) { | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The silent truncation of the system prompt at 3500 characters is risky. If the prompt grows in the future, this could cut off critical instructions (such as the clarification mode logic or the JSON schema definition) at the end of the string. This would lead to unpredictable LLM behavior or parsing errors. Consider adding a check to log a warning or throw an error if the collapsed prompt exceeds this limit, rather than silently slicing it.