fixed bugs

This commit is contained in:
Sreeram Sreedhar 2026-04-18 14:53:56 -07:00
parent 039f5fc34d
commit e8252b1048

View file

@ -1,3 +1,4 @@
import Supermemory from "supermemory"
import { deduplicateMemories } from "../tools-shared"
import type {
Logger,
@ -11,6 +12,7 @@ import {
convertProfileToMarkdown,
defaultPromptTemplate,
} from "./prompt-builder"
import { createSupermemoryClient } from "./context"
/**
* Fetches profile and search results from the Supermemory API.
@ -84,10 +86,142 @@ export interface BuildMemoriesTextOptions {
searchLimit?: number
}
/**
* Search result structure for unified handling.
*/
interface SearchResult {
content: string
metadata?: Record<string, unknown>
}
/**
* Searches for memories using the Supermemory SDK.
*
* @param client - Supermemory client instance
* @param containerTag - Container tag for scoping
* @param query - Search query text
* @param limit - Maximum number of results
* @returns Array of search results
*/
async function searchMemoriesSDK(
client: Supermemory,
containerTag: string,
query: string,
limit: number,
): Promise<SearchResult[]> {
const response = await client.search.memories({
q: query,
containerTag,
limit,
include: { chunks: false },
})
return (response.results || []).map((r) => ({
content: r.memory || "",
metadata: r.metadata ?? undefined,
}))
}
/**
* Searches for document chunks using the Supermemory SDK.
* Only includes chunks marked as relevant (isRelevant: true).
*
* @param client - Supermemory client instance
* @param containerTag - Container tag for scoping
* @param query - Search query text
* @param limit - Maximum number of results
* @returns Array of search results containing only relevant chunks
*/
async function searchDocumentsSDK(
client: Supermemory,
containerTag: string,
query: string,
limit: number,
): Promise<SearchResult[]> {
const response = await client.search.documents({
q: query,
containerTags: [containerTag],
limit,
})
// Extract only relevant chunks from each document result
const results: SearchResult[] = []
for (const doc of response.results || []) {
for (const chunk of doc.chunks || []) {
// Only include chunks marked as relevant
if (chunk.isRelevant) {
results.push({
content: chunk.content || "",
metadata: doc.metadata ?? undefined,
})
}
}
}
return results
}
/**
* Performs search based on the specified search mode.
*
* @param client - Supermemory client instance
* @param containerTag - Container tag for scoping
* @param query - Search query text
* @param searchMode - Search mode: "memories", "hybrid", or "documents"
* @param limit - Maximum number of results per search type
* @param logger - Logger instance
* @returns Combined array of search results
*/
async function performSearch(
client: Supermemory,
containerTag: string,
query: string,
searchMode: SearchMode,
limit: number,
logger: Logger,
): Promise<SearchResult[]> {
logger.debug("Performing search", { searchMode, containerTag, limit })
switch (searchMode) {
case "memories":
return searchMemoriesSDK(client, containerTag, query, limit)
case "documents":
return searchDocumentsSDK(client, containerTag, query, limit)
case "hybrid": {
// Run both searches in parallel
const [memoriesResults, documentsResults] = await Promise.all([
searchMemoriesSDK(client, containerTag, query, limit),
searchDocumentsSDK(client, containerTag, query, limit),
])
logger.debug("Hybrid search completed", {
memoriesCount: memoriesResults.length,
documentsCount: documentsResults.length,
})
// Combine results, memories first
return [...memoriesResults, ...documentsResults]
}
default:
logger.warn(`Unknown search mode: ${searchMode}, defaulting to memories`)
return searchMemoriesSDK(client, containerTag, query, limit)
}
}
/**
* Fetches memories from the API, deduplicates them, and formats them into
* the final string to be injected into the system prompt.
*
* When searchMode is specified, uses the Supermemory SDK search endpoints:
* - "memories": Uses search.memories() for memory entries
* - "documents": Uses search.documents() for document chunks
* - "hybrid": Uses both endpoints in parallel
*
* The mode option controls whether profile data is included:
* - "profile": Only profile data (static/dynamic), no search
* - "query": Only search results, no profile data
* - "full": Both profile data and search results
*
* @param options - Configuration for building memories text
* @returns The final formatted memories string ready for injection
*/
@ -102,48 +236,77 @@ export const buildMemoriesText = async (
apiKey,
logger,
promptTemplate = defaultPromptTemplate,
searchMode = "memories",
searchLimit = 10,
} = options
const memoriesResponse = await supermemoryProfileSearch(
containerTag,
queryText,
baseUrl,
apiKey,
)
// Fetch profile data when mode includes profile (profile or full)
let profileData: ProfileStructure | null = null
if (mode !== "query") {
profileData = await supermemoryProfileSearch(
containerTag,
mode === "profile" ? "" : queryText, // Only send query for full mode
baseUrl,
apiKey,
)
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0
const memoryCountStatic = profileData.profile.static?.length || 0
const memoryCountDynamic = profileData.profile.dynamic?.length || 0
logger.info("Memory search completed", {
containerTag,
memoryCountStatic,
memoryCountDynamic,
queryText:
queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
mode,
})
logger.info("Profile search completed", {
containerTag,
memoryCountStatic,
memoryCountDynamic,
mode,
})
}
// Perform SDK-based search when mode includes query (query or full)
let searchResults: SearchResult[] = []
if (mode !== "profile" && queryText) {
const client = createSupermemoryClient({ apiKey, baseUrl })
searchResults = await performSearch(
client,
containerTag,
queryText,
searchMode,
searchLimit,
logger,
)
logger.info("Search completed", {
containerTag,
searchMode,
searchLimit,
resultCount: searchResults.length,
queryText:
queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
})
}
// Deduplicate profile memories
const deduplicated = deduplicateMemories({
static: memoriesResponse.profile.static,
dynamic: memoriesResponse.profile.dynamic,
searchResults: memoriesResponse.searchResults?.results,
static: profileData?.profile.static,
dynamic: profileData?.profile.dynamic,
searchResults: searchResults.map((r) => ({ memory: r.content })),
})
logger.debug("Memory deduplication completed", {
static: {
original: memoryCountStatic,
original: profileData?.profile.static?.length || 0,
deduplicated: deduplicated.static.length,
},
dynamic: {
original: memoryCountDynamic,
original: profileData?.profile.dynamic?.length || 0,
deduplicated: deduplicated.dynamic.length,
},
searchResults: {
original: memoriesResponse.searchResults?.results?.length,
original: searchResults.length,
deduplicated: deduplicated.searchResults?.length,
},
})
// Build user memories from profile (static + dynamic)
const userMemories =
mode !== "query"
? convertProfileToMarkdown({
@ -154,9 +317,11 @@ export const buildMemoriesText = async (
searchResults: { results: [] },
})
: ""
// Build search results text
const generalSearchMemories =
mode !== "profile"
? `Search results for user's recent message: \n${deduplicated.searchResults
mode !== "profile" && deduplicated.searchResults.length > 0
? `Search results for user's recent message:\n${deduplicated.searchResults
.map((memory) => `- ${memory}`)
.join("\n")}`
: ""
@ -164,7 +329,10 @@ export const buildMemoriesText = async (
const promptData: MemoryPromptData = {
userMemories,
generalSearchMemories,
searchResults: memoriesResponse.searchResults?.results ?? [],
searchResults: searchResults.map((r) => ({
memory: r.content,
metadata: r.metadata,
})),
}
const memories = promptTemplate(promptData)