mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fix(tools,validation): persist tool-call turns in conversation memory & tidy search thresholds (#1211)
Co-authored-by: Dhravya Shah <dhravyashah@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
501613b6bc
commit
2163f592b7
6 changed files with 576 additions and 34 deletions
|
|
@ -140,6 +140,18 @@ const model = withSupermemory(openai("gpt-5"), {
|
|||
})
|
||||
```
|
||||
|
||||
### Persisting Tool Calls (default: off)
|
||||
|
||||
By default, saved conversations include only user and assistant text — tool calls and tool results are dropped, since tool payloads are often large and low-signal and would pollute memory extraction. To persist the full tool round trip (tool calls with their arguments, plus tool results, in their original order), set `includeToolCalls: true`:
|
||||
|
||||
```typescript
|
||||
const model = withSupermemory(openai("gpt-5"), {
|
||||
containerTag: "user-123",
|
||||
customId: "conv-1",
|
||||
includeToolCalls: true,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Memory Tools
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ interface WrapVercelLanguageModelOptions {
|
|||
apiKey?: string
|
||||
/** Custom Supermemory API base URL */
|
||||
baseUrl?: string
|
||||
/**
|
||||
* Persist assistant tool calls and tool results as part of the saved
|
||||
* conversation. Off by default: tool payloads are often large and
|
||||
* low-signal, and would pollute memory extraction.
|
||||
*/
|
||||
includeToolCalls?: boolean
|
||||
/**
|
||||
* Custom function to format memory data into the system prompt.
|
||||
* If not provided, uses the default "User Supermemories:" format.
|
||||
|
|
@ -134,6 +140,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
|
|||
mode: options.mode ?? "profile",
|
||||
addMemory: options.addMemory ?? "always",
|
||||
baseUrl: options.baseUrl,
|
||||
includeToolCalls: options.includeToolCalls ?? false,
|
||||
promptTemplate: options.promptTemplate,
|
||||
memoryRetrievalTimeoutMs: DEFAULT_MEMORY_RETRIEVAL_TIMEOUT_MS,
|
||||
})
|
||||
|
|
@ -193,6 +200,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
|
|||
ctx.logger,
|
||||
ctx.apiKey,
|
||||
ctx.normalizedBaseUrl,
|
||||
ctx.includeToolCalls,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -268,6 +276,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
|
|||
ctx.logger,
|
||||
ctx.apiKey,
|
||||
ctx.normalizedBaseUrl,
|
||||
ctx.includeToolCalls,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Supermemory from "supermemory"
|
||||
import {
|
||||
addConversation,
|
||||
type ContentPart,
|
||||
type ConversationMessage,
|
||||
} from "../conversations-client"
|
||||
import {
|
||||
|
|
@ -15,16 +16,57 @@ import {
|
|||
import { type LanguageModelCallOptions, getLastUserMessage } from "./util"
|
||||
import { extractQueryText, injectMemoriesIntoParams } from "./memory-prompt"
|
||||
|
||||
const convertToConversationMessages = (
|
||||
const safeJsonStringify = (value: unknown): string => {
|
||||
try {
|
||||
return JSON.stringify(value) ?? ""
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
const serializeToolOutput = (output: unknown): string => {
|
||||
if (typeof output === "string") return output
|
||||
if (typeof output !== "object" || output === null) {
|
||||
return safeJsonStringify(output)
|
||||
}
|
||||
|
||||
const wrapper = output as {
|
||||
type?: unknown
|
||||
value?: unknown
|
||||
reason?: unknown
|
||||
}
|
||||
|
||||
if (
|
||||
(wrapper.type === "text" || wrapper.type === "error-text") &&
|
||||
typeof wrapper.value === "string"
|
||||
) {
|
||||
return wrapper.value
|
||||
}
|
||||
if (
|
||||
wrapper.type === "json" ||
|
||||
wrapper.type === "error-json" ||
|
||||
wrapper.type === "content"
|
||||
) {
|
||||
return safeJsonStringify(wrapper.value)
|
||||
}
|
||||
if (wrapper.type === "execution-denied") {
|
||||
return typeof wrapper.reason === "string" && wrapper.reason
|
||||
? wrapper.reason
|
||||
: "Tool execution denied"
|
||||
}
|
||||
|
||||
return safeJsonStringify(output)
|
||||
}
|
||||
|
||||
export const convertToConversationMessages = (
|
||||
params: LanguageModelCallOptions,
|
||||
assistantResponseText: string,
|
||||
includeToolCalls = false,
|
||||
): ConversationMessage[] => {
|
||||
const messages: ConversationMessage[] = []
|
||||
|
||||
for (const msg of params.prompt) {
|
||||
if (msg.role === "system") {
|
||||
continue
|
||||
}
|
||||
if (msg.role === "system") continue
|
||||
|
||||
if (typeof msg.content === "string") {
|
||||
if (msg.content) {
|
||||
|
|
@ -33,36 +75,67 @@ const convertToConversationMessages = (
|
|||
content: msg.content,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const contentParts = msg.content
|
||||
.map((c) => {
|
||||
if (c.type === "text" && c.text) {
|
||||
return {
|
||||
type: "text" as const,
|
||||
text: c.text,
|
||||
}
|
||||
}
|
||||
if (
|
||||
c.type === "file" &&
|
||||
typeof c.data === "string" &&
|
||||
c.mediaType.startsWith("image/")
|
||||
) {
|
||||
return {
|
||||
type: "image_url" as const,
|
||||
image_url: { url: c.data },
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((part) => part !== null)
|
||||
continue
|
||||
}
|
||||
|
||||
if (contentParts.length > 0) {
|
||||
let contentParts: ContentPart[] = []
|
||||
let toolCalls: NonNullable<ConversationMessage["tool_calls"]> = []
|
||||
|
||||
// Flush any pending assistant/user content accumulated so far. Called
|
||||
// before each tool-result so a tool result never jumps ahead of the
|
||||
// text/tool-calls that preceded it (or behind text that follows it),
|
||||
// preserving the original chronology for memory extraction.
|
||||
const flushContent = () => {
|
||||
if (contentParts.length > 0 || toolCalls.length > 0) {
|
||||
messages.push({
|
||||
role: msg.role as "user" | "assistant" | "tool",
|
||||
content: contentParts,
|
||||
content: contentParts.length > 0 ? contentParts : "",
|
||||
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
||||
})
|
||||
contentParts = []
|
||||
toolCalls = []
|
||||
}
|
||||
}
|
||||
|
||||
for (const content of msg.content) {
|
||||
if (content.type === "text" && content.text) {
|
||||
contentParts.push({
|
||||
type: "text",
|
||||
text: content.text,
|
||||
})
|
||||
} else if (
|
||||
content.type === "file" &&
|
||||
typeof content.data === "string" &&
|
||||
content.mediaType.startsWith("image/")
|
||||
) {
|
||||
contentParts.push({
|
||||
type: "image_url",
|
||||
image_url: { url: content.data },
|
||||
})
|
||||
} else if (
|
||||
includeToolCalls &&
|
||||
content.type === "tool-call" &&
|
||||
msg.role === "assistant"
|
||||
) {
|
||||
toolCalls.push({
|
||||
id: content.toolCallId,
|
||||
type: "function",
|
||||
function: {
|
||||
name: content.toolName,
|
||||
arguments: safeJsonStringify(content.input) || "{}",
|
||||
},
|
||||
})
|
||||
} else if (includeToolCalls && content.type === "tool-result") {
|
||||
flushContent()
|
||||
messages.push({
|
||||
role: "tool",
|
||||
content: serializeToolOutput(content.output),
|
||||
tool_call_id: content.toolCallId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
flushContent()
|
||||
}
|
||||
|
||||
if (assistantResponseText) {
|
||||
|
|
@ -84,11 +157,13 @@ export const saveMemoryAfterResponse = async (
|
|||
logger: Logger,
|
||||
apiKey: string,
|
||||
baseUrl: string,
|
||||
includeToolCalls = false,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const conversationMessages = convertToConversationMessages(
|
||||
params,
|
||||
assistantResponseText,
|
||||
includeToolCalls,
|
||||
)
|
||||
|
||||
const response = await addConversation({
|
||||
|
|
@ -139,6 +214,12 @@ interface SupermemoryMiddlewareOptions {
|
|||
addMemory?: "always" | "never"
|
||||
/** Custom Supermemory API base URL */
|
||||
baseUrl?: string
|
||||
/**
|
||||
* Persist assistant tool calls and tool results as part of the saved
|
||||
* conversation. Off by default: tool payloads are often large and
|
||||
* low-signal, and would pollute memory extraction.
|
||||
*/
|
||||
includeToolCalls?: boolean
|
||||
/** Custom function to format memory data into the system prompt */
|
||||
promptTemplate?: PromptTemplate
|
||||
/** Max wait (ms) for the pre-LLM `/v4/profile` retrieval. Omit for no limit (e.g. tests). `withSupermemory` sets this internally. */
|
||||
|
|
@ -152,6 +233,7 @@ interface SupermemoryMiddlewareContext {
|
|||
customId: string
|
||||
mode: MemoryMode
|
||||
addMemory: "always" | "never"
|
||||
includeToolCalls: boolean
|
||||
normalizedBaseUrl: string
|
||||
apiKey: string
|
||||
promptTemplate?: PromptTemplate
|
||||
|
|
@ -174,6 +256,7 @@ export const createSupermemoryContext = (
|
|||
mode = "profile",
|
||||
addMemory = "always",
|
||||
baseUrl,
|
||||
includeToolCalls = false,
|
||||
promptTemplate,
|
||||
memoryRetrievalTimeoutMs,
|
||||
} = options
|
||||
|
|
@ -195,6 +278,7 @@ export const createSupermemoryContext = (
|
|||
customId,
|
||||
mode,
|
||||
addMemory,
|
||||
includeToolCalls,
|
||||
normalizedBaseUrl,
|
||||
apiKey,
|
||||
promptTemplate,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,358 @@
|
|||
import type Supermemory from "supermemory"
|
||||
import type {
|
||||
LanguageModelV2CallOptions,
|
||||
LanguageModelV2Message,
|
||||
} from "@ai-sdk/provider"
|
||||
import { afterEach, describe, expect, it } from "vitest"
|
||||
import { createLogger } from "../../src/shared"
|
||||
import { saveMemoryAfterResponse } from "../../src/vercel/middleware"
|
||||
|
||||
const originalFetch = globalThis.fetch
|
||||
|
||||
const persistMessages = async (
|
||||
params: LanguageModelV2CallOptions,
|
||||
assistantResponseText: string,
|
||||
includeToolCalls = true,
|
||||
) => {
|
||||
let messages: unknown[] | undefined
|
||||
|
||||
const fetchStub: typeof fetch = Object.assign(
|
||||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = typeof input === "string" ? input : input.toString()
|
||||
expect(url).toContain("/v4/conversations")
|
||||
|
||||
const body = typeof init?.body === "string" ? init.body : ""
|
||||
messages = (JSON.parse(body) as { messages: unknown[] }).messages
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: "document-id",
|
||||
conversationId: "conversation-id",
|
||||
status: "done",
|
||||
}),
|
||||
{ status: 200 },
|
||||
)
|
||||
},
|
||||
{ preconnect: originalFetch.preconnect },
|
||||
)
|
||||
|
||||
globalThis.fetch = fetchStub
|
||||
|
||||
await saveMemoryAfterResponse(
|
||||
{} as Supermemory,
|
||||
"user-id",
|
||||
"conversation-id",
|
||||
assistantResponseText,
|
||||
params,
|
||||
createLogger(false),
|
||||
"test-api-key",
|
||||
"https://api.example.com",
|
||||
includeToolCalls,
|
||||
)
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch
|
||||
})
|
||||
|
||||
describe("convertToConversationMessages", () => {
|
||||
it("preserves a tool-call and tool-result round trip", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Search my memories" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-1",
|
||||
toolName: "search",
|
||||
input: { query: "project" },
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
{
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-1",
|
||||
toolName: "search",
|
||||
output: {
|
||||
type: "json",
|
||||
value: { memory: "Project memory" },
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "Found it")).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Search my memories" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call-1",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "search",
|
||||
arguments: '{"query":"project"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "tool",
|
||||
content: '{"memory":"Project memory"}',
|
||||
tool_call_id: "call-1",
|
||||
},
|
||||
{ role: "assistant", content: "Found it" },
|
||||
])
|
||||
})
|
||||
|
||||
it("keeps assistant text alongside tool calls", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "I will search." },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-2",
|
||||
toolName: "search",
|
||||
input: {},
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "")).toEqual([
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "I will search." }],
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call-2",
|
||||
type: "function",
|
||||
function: { name: "search", arguments: "{}" },
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("serializes a tool call with no input as empty JSON object", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-4",
|
||||
toolName: "now",
|
||||
input: undefined,
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "")).toEqual([
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call-4",
|
||||
type: "function",
|
||||
function: { name: "now", arguments: "{}" },
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("does not abort the save when tool-call input is not JSON-serializable", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "hi" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-5",
|
||||
toolName: "search",
|
||||
input: { cursor: BigInt(1) },
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "done")).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "hi" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call-5",
|
||||
type: "function",
|
||||
function: { name: "search", arguments: "{}" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ role: "assistant", content: "done" },
|
||||
])
|
||||
})
|
||||
|
||||
it("preserves order when tool result is followed by assistant text in one message", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-6",
|
||||
toolName: "search",
|
||||
input: { query: "project" },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-6",
|
||||
toolName: "search",
|
||||
output: {
|
||||
type: "json",
|
||||
value: { memory: "Project memory" },
|
||||
},
|
||||
},
|
||||
{ type: "text", text: "Here is what I found." },
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "")).toEqual([
|
||||
{
|
||||
role: "assistant",
|
||||
content: "",
|
||||
tool_calls: [
|
||||
{
|
||||
id: "call-6",
|
||||
type: "function",
|
||||
function: {
|
||||
name: "search",
|
||||
arguments: '{"query":"project"}',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "tool",
|
||||
content: '{"memory":"Project memory"}',
|
||||
tool_call_id: "call-6",
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Here is what I found." }],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("unwraps text tool output", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-3",
|
||||
toolName: "search",
|
||||
output: {
|
||||
type: "text",
|
||||
value: "No memories found",
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "")).toEqual([
|
||||
{
|
||||
role: "tool",
|
||||
content: "No memories found",
|
||||
tool_call_id: "call-3",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("drops tool calls and tool results by default", async () => {
|
||||
const params: LanguageModelV2CallOptions = {
|
||||
prompt: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Search my memories" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "I will search." },
|
||||
{
|
||||
type: "tool-call",
|
||||
toolCallId: "call-7",
|
||||
toolName: "search",
|
||||
input: { query: "project" },
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
{
|
||||
role: "tool",
|
||||
content: [
|
||||
{
|
||||
type: "tool-result",
|
||||
toolCallId: "call-7",
|
||||
toolName: "search",
|
||||
output: { type: "json", value: { memory: "Project memory" } },
|
||||
},
|
||||
],
|
||||
} as unknown as LanguageModelV2Message,
|
||||
],
|
||||
}
|
||||
|
||||
expect(await persistMessages(params, "Found it", false)).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Search my memories" }],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "I will search." }],
|
||||
},
|
||||
{ role: "assistant", content: "Found it" },
|
||||
])
|
||||
})
|
||||
})
|
||||
82
packages/validation/api.test.ts
Normal file
82
packages/validation/api.test.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { describe, expect, it } from "bun:test"
|
||||
import { readFileSync } from "node:fs"
|
||||
import { SearchRequestSchema, Searchv4RequestSchema } from "./api"
|
||||
|
||||
describe("search threshold schemas", () => {
|
||||
it("do not contain redundant number transforms or unreachable range guards", () => {
|
||||
const source = readFileSync(new URL("./api.ts", import.meta.url), "utf8")
|
||||
const searchSchemas = source.slice(
|
||||
source.indexOf("export const SearchRequestSchema"),
|
||||
source.indexOf("export const SearchResultSchema"),
|
||||
)
|
||||
|
||||
expect(searchSchemas).not.toContain(".transform(Number)")
|
||||
expect(searchSchemas).not.toContain("v === undefined || (v >= 0 && v <= 1)")
|
||||
})
|
||||
|
||||
it("preserves threshold defaults", () => {
|
||||
const search = SearchRequestSchema.parse({ q: "memory" })
|
||||
const searchV4 = Searchv4RequestSchema.parse({ q: "memory" })
|
||||
|
||||
expect(search.chunkThreshold).toBe(0)
|
||||
expect(search.documentThreshold).toBe(0)
|
||||
expect(searchV4.threshold).toBe(0.6)
|
||||
})
|
||||
|
||||
it.each([0, 0.5, 1])("accepts inclusive threshold value %p", (threshold) => {
|
||||
expect(
|
||||
SearchRequestSchema.parse({
|
||||
q: "memory",
|
||||
chunkThreshold: threshold,
|
||||
documentThreshold: threshold,
|
||||
}),
|
||||
).toMatchObject({
|
||||
chunkThreshold: threshold,
|
||||
documentThreshold: threshold,
|
||||
})
|
||||
expect(
|
||||
Searchv4RequestSchema.parse({ q: "memory", threshold }).threshold,
|
||||
).toBe(threshold)
|
||||
})
|
||||
|
||||
it.each([
|
||||
-0.1, 1.1,
|
||||
])("rejects out-of-range threshold value %p", (threshold) => {
|
||||
expect(
|
||||
SearchRequestSchema.safeParse({
|
||||
q: "memory",
|
||||
chunkThreshold: threshold,
|
||||
}).success,
|
||||
).toBe(false)
|
||||
expect(
|
||||
SearchRequestSchema.safeParse({
|
||||
q: "memory",
|
||||
documentThreshold: threshold,
|
||||
}).success,
|
||||
).toBe(false)
|
||||
expect(
|
||||
Searchv4RequestSchema.safeParse({ q: "memory", threshold }).success,
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it("does not coerce threshold strings", () => {
|
||||
expect(
|
||||
SearchRequestSchema.safeParse({
|
||||
q: "memory",
|
||||
chunkThreshold: "0.5",
|
||||
}).success,
|
||||
).toBe(false)
|
||||
expect(
|
||||
SearchRequestSchema.safeParse({
|
||||
q: "memory",
|
||||
documentThreshold: "0.5",
|
||||
}).success,
|
||||
).toBe(false)
|
||||
expect(
|
||||
Searchv4RequestSchema.safeParse({
|
||||
q: "memory",
|
||||
threshold: "0.5",
|
||||
}).success,
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
@ -346,14 +346,13 @@ export const SearchRequestSchema = z.object({
|
|||
.number()
|
||||
.optional()
|
||||
.default(0)
|
||||
.refine((v) => v === undefined || (v >= 0 && v <= 1), {
|
||||
.refine((v) => v >= 0 && v <= 1, {
|
||||
message: "chunkThreshold must be between 0 and 1",
|
||||
params: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
},
|
||||
})
|
||||
.transform(Number)
|
||||
.openapi({
|
||||
description:
|
||||
"Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most chunks, more results), 1 is most sensitive (returns lesser chunks, accurate results)",
|
||||
|
|
@ -378,14 +377,13 @@ export const SearchRequestSchema = z.object({
|
|||
.number()
|
||||
.optional()
|
||||
.default(0)
|
||||
.refine((v) => v === undefined || (v >= 0 && v <= 1), {
|
||||
.refine((v) => v >= 0 && v <= 1, {
|
||||
message: "documentThreshold must be between 0 and 1",
|
||||
params: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
},
|
||||
})
|
||||
.transform(Number)
|
||||
.openapi({
|
||||
description:
|
||||
"Threshold / sensitivity for document selection. 0 is least sensitive (returns most documents, more results), 1 is most sensitive (returns lesser documents, accurate results)",
|
||||
|
|
@ -473,14 +471,13 @@ export const Searchv4RequestSchema = z.object({
|
|||
.number()
|
||||
.optional()
|
||||
.default(0.6)
|
||||
.refine((v) => v === undefined || (v >= 0 && v <= 1), {
|
||||
.refine((v) => v >= 0 && v <= 1, {
|
||||
message: "documentThreshold must be between 0 and 1",
|
||||
params: {
|
||||
max: 1,
|
||||
min: 0,
|
||||
},
|
||||
})
|
||||
.transform(Number)
|
||||
.openapi({
|
||||
description:
|
||||
"Threshold / sensitivity for memories selection. 0 is least sensitive (returns most memories, more results), 1 is most sensitive (returns lesser memories, accurate results)",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue