mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-02 02:11:20 +00:00
fix(tools): stop forcing empty instructions on Responses requests
The Responses middleware always set `instructions` on the forwarded
request, even when the caller never passed one and there were no
memories to inject. `replaceMemoryContext("", "")` returns `""`, so
those requests went out with `instructions: ""`.
Sending the field at all tells the Responses API to drop the
instructions carried over from a `previous_response_id` turn, so an
empty string silently wipes the caller's system prompt mid-conversation
instead of leaving it in place.
The no-input early return in the same function already gets this right —
it spreads `instructions` only when the caller supplied a string. Apply
the same rule to the main path, while still injecting when there are
memories to add.
Adds three regression tests to the existing OpenAI middleware unit
suite, which CI already runs on changes to packages/tools.
This commit is contained in:
parent
4d8a4ebfdd
commit
66c05271a1
2 changed files with 108 additions and 1 deletions
|
|
@ -970,13 +970,22 @@ export function createOpenAIMiddleware(
|
|||
: ""
|
||||
}
|
||||
|
||||
// Sending `instructions` at all tells the Responses API to drop whatever a
|
||||
// `previous_response_id` turn carried over, so an empty result must leave
|
||||
// the caller's own field untouched rather than blank it. Matches the
|
||||
// no-input early return above.
|
||||
const instructionsOverride =
|
||||
enhancedInstructions || typeof params.instructions === "string"
|
||||
? { instructions: enhancedInstructions }
|
||||
: {}
|
||||
|
||||
return {
|
||||
request: originalResponsesCreate.call(
|
||||
openaiClient.responses,
|
||||
{
|
||||
...params,
|
||||
input: cleanedInput,
|
||||
instructions: enhancedInstructions,
|
||||
...instructionsOverride,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -63,3 +63,101 @@ describe("OpenAI middleware memory context", () => {
|
|||
).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("OpenAI Responses middleware instructions", () => {
|
||||
const originalApiKey = process.env.SUPERMEMORY_API_KEY
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.SUPERMEMORY_API_KEY = "sm_test_key"
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalApiKey === undefined) delete process.env.SUPERMEMORY_API_KEY
|
||||
else process.env.SUPERMEMORY_API_KEY = originalApiKey
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
const stubMemoryFetch = (staticMemories: string[]) => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
profile: {
|
||||
static: staticMemories.map((memory) => ({ memory })),
|
||||
dynamic: [],
|
||||
},
|
||||
searchResults: { results: [] },
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const wrapResponsesClient = () => {
|
||||
const originalCreate = vi.fn(() =>
|
||||
Object.assign(Promise.resolve({ output: [] }), {
|
||||
asResponse: async () => new Response(),
|
||||
}),
|
||||
)
|
||||
const client = {
|
||||
chat: { completions: { create: vi.fn() } },
|
||||
responses: { create: originalCreate },
|
||||
} as unknown as OpenAI
|
||||
const wrapped = withSupermemory(client, {
|
||||
containerTag: "user-a",
|
||||
customId: "conversation-a",
|
||||
mode: "profile",
|
||||
addMemory: "never",
|
||||
})
|
||||
return { originalCreate, wrapped }
|
||||
}
|
||||
|
||||
it("leaves instructions absent when the caller sent none and there is nothing to inject", async () => {
|
||||
stubMemoryFetch([])
|
||||
const { originalCreate, wrapped } = wrapResponsesClient()
|
||||
|
||||
await wrapped.responses.create({
|
||||
model: "gpt-4o-mini",
|
||||
input: "What do you remember?",
|
||||
previous_response_id: "resp_123",
|
||||
})
|
||||
|
||||
// An empty `instructions` still counts as present, and the Responses API
|
||||
// drops the previous turn's instructions whenever the field is sent.
|
||||
const forwarded = originalCreate.mock.calls[0]?.[0]
|
||||
expect("instructions" in forwarded).toBe(false)
|
||||
expect(forwarded.previous_response_id).toBe("resp_123")
|
||||
})
|
||||
|
||||
it("still injects memories as instructions when the caller sent none", async () => {
|
||||
stubMemoryFetch(["Fresh profile fact"])
|
||||
const { originalCreate, wrapped } = wrapResponsesClient()
|
||||
|
||||
await wrapped.responses.create({
|
||||
model: "gpt-4o-mini",
|
||||
input: "What do you remember?",
|
||||
})
|
||||
|
||||
const forwarded = originalCreate.mock.calls[0]?.[0]
|
||||
expect(String(forwarded.instructions)).toContain("Fresh profile fact")
|
||||
})
|
||||
|
||||
it("keeps caller instructions and strips stale context when there is nothing to inject", async () => {
|
||||
stubMemoryFetch([])
|
||||
const { originalCreate, wrapped } = wrapResponsesClient()
|
||||
|
||||
await wrapped.responses.create({
|
||||
model: "gpt-4o-mini",
|
||||
input: "What do you remember?",
|
||||
instructions: [
|
||||
"Be helpful.",
|
||||
'<supermemory context="user-memories" readonly>',
|
||||
"Stale profile fact",
|
||||
"</supermemory>",
|
||||
].join("\n"),
|
||||
})
|
||||
|
||||
const forwarded = originalCreate.mock.calls[0]?.[0]
|
||||
expect(String(forwarded.instructions)).toBe("Be helpful.")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue