Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 78 additions & 34 deletions apps/sdk-playground/python/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ async def fetch_profile_context(
container_tag: str,
sm_key: str,
query: Optional[str] = None,
*,
include: Optional[list[str]] = None,
) -> dict[str, list[Any]]:
from supermemory import AsyncSupermemory

Expand All @@ -351,10 +353,12 @@ async def fetch_profile_context(
base_url=supermemory_base_url(),
timeout=HTTP_TIMEOUT_SECONDS,
)
profile_response = await client.profile(
container_tag=container_tag,
**({"q": query} if query else {}),
)
request: dict[str, Any] = {"container_tag": container_tag}
if query:
request["q"] = query
if include is not None:
request["include"] = include
profile_response = await client.profile(**request)
return extract_profile_context(profile_response)


Expand Down Expand Up @@ -530,6 +534,49 @@ async def fetch_container_context(
}


def reconstruct_python_sdk_memory_block(
memory_mode: str,
profile: dict[str, Any],
) -> tuple[dict[str, list[str]], str]:
from supermemory_openai import convert_profile_to_markdown, deduplicate_memories
from supermemory_openai.utils import wrap_memory_context

deduplicated = deduplicate_memories(
static=profile.get("static", []) if memory_mode != "query" else [],
dynamic=profile.get("dynamic", []) if memory_mode != "query" else [],
search_results=profile.get("searchResults", []),
)
visible_profile = {
"static": deduplicated.static,
"dynamic": deduplicated.dynamic,
"searchResults": (
[] if memory_mode == "profile" else deduplicated.search_results
),
}

profile_data = ""
if memory_mode != "query":
profile_data = convert_profile_to_markdown(
{
"profile": {
"static": visible_profile["static"],
"dynamic": visible_profile["dynamic"],
},
"searchResults": {"results": []},
}
)

search_results_memories = ""
if memory_mode != "profile" and visible_profile["searchResults"]:
search_results_memories = (
"Search results for user's recent message: \n"
+ "\n".join(f"- {memory}" for memory in visible_profile["searchResults"])
)

memories = f"{profile_data}\n{search_results_memories}".strip()
return visible_profile, wrap_memory_context(memories)


def build_middleware_memory_debug(
container_tag: str,
conversation_id: str,
Expand All @@ -549,39 +596,20 @@ def build_middleware_memory_debug(
}
)
else:
profile = context["profile"]
preview_lines = [
f"[memory mode: {memory_mode}]",
"[post-response snapshot; not the exact middleware prompt]",
]
if context.get("query"):
preview_lines.append(f"[query: {context['query']}]")

selected_sections: list[tuple[str, list[Any]]] = []
if memory_mode in ("profile", "full"):
selected_sections.extend(
(
("Static", profile.get("static", [])),
("Dynamic", profile.get("dynamic", [])),
)
)
if memory_mode in ("query", "full"):
selected_sections.append(
("Search results", profile.get("searchResults", []))
)

for label, items in selected_sections:
if items:
preview_lines.append(f"{label}:")
for item in items[:8]:
preview_lines.append(f"- {display_context_item(item)}")
raw_profile = context["profile"]
profile, memory_block = reconstruct_python_sdk_memory_block(
memory_mode,
raw_profile,
)

debug.extend(
(
{
"type": "profile_fetch",
"label": "Post-response profile snapshot",
"label": "Post-response context reconstruction",
"detail": {
"authoritativeMiddlewareCapture": False,
"timing": "after model response",
"endpoint": "POST /v4/profile",
"containerTag": container_tag,
"customId": conversation_id,
Expand All @@ -594,8 +622,19 @@ def build_middleware_memory_debug(
},
{
"type": "context_preview",
"label": "Post-response context preview",
"preview": "\n".join(preview_lines),
"label": (
"Reconstructed SDK-owned memory block "
"(not middleware capture)"
),
"preview": memory_block,
"detail": {
"totalFacts": (
len(profile.get("static", []))
+ len(profile.get("dynamic", []))
+ len(profile.get("searchResults", []))
),
"fullLength": len(memory_block),
},
},
)
)
Expand Down Expand Up @@ -632,7 +671,12 @@ async def fetch_context_for_debug(
) -> tuple[Optional[dict[str, Any]], Optional[str]]:
try:
async with asyncio.timeout(CONTEXT_DEBUG_TIMEOUT_SECONDS):
profile = await fetch_profile_context(container_tag, sm_key, query)
profile = await fetch_profile_context(
container_tag,
sm_key,
query,
include=["static", "dynamic"],
)
return (
{
"containerTag": container_tag,
Expand Down
3 changes: 2 additions & 1 deletion apps/sdk-playground/src/lib/chat-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,11 @@ export async function runTypeScriptChat(
middlewareConfig,
request.sdkId === "ts-ai-sdk-middleware"
? {
flavor: "ai-sdk",
includeToolCalls: middlewareConfig.includeToolCalls,
skipMemoryOnError: middlewareConfig.skipMemoryOnError,
}
: undefined,
: { flavor: "openai" },
keys.supermemoryApiKey,
signal,
),
Expand Down
97 changes: 39 additions & 58 deletions apps/sdk-playground/src/lib/context-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import {
type MiddlewareRuntimeConfig,
normalizeMiddlewareConfig,
} from "./middleware-config"
import {
type MemoryMode,
type MiddlewareFlavor,
reconstructSdkMemoryBlock,
} from "./memory-dedupe"

export interface MemoryDebugEntry {
type:
Expand Down Expand Up @@ -91,45 +96,6 @@ function summarizeProfile(profile: ContainerContext["profile"]) {
}
}

function selectProfileForMode(
profile: ContainerContext["profile"],
mode: "profile" | "query" | "full",
): ContainerContext["profile"] {
return {
static: mode === "query" ? [] : profile.static,
dynamic: mode === "query" ? [] : profile.dynamic,
searchResults: mode === "profile" ? [] : profile.searchResults,
}
}

function buildContextPreview(
profile: ContainerContext["profile"],
mode: "profile" | "query" | "full",
query?: string,
): string {
const lines: string[] = [`[memory mode: ${mode}]`]
if (query) lines.push(`[query: ${query}]`)
if (mode !== "query" && profile.static.length) {
lines.push("Static:")
for (const item of profile.static.slice(0, 8)) {
lines.push(`- ${memoryText(item)}`)
}
}
if (mode !== "query" && profile.dynamic.length) {
lines.push("Dynamic:")
for (const item of profile.dynamic.slice(0, 8)) {
lines.push(`- ${memoryText(item)}`)
}
}
if (mode !== "profile" && profile.searchResults.length) {
lines.push("Search results:")
for (const item of profile.searchResults.slice(0, 8)) {
lines.push(`- ${memoryText(item)}`)
}
}
return lines.join("\n")
}

function normalizeSearchResults(searchResults: unknown): unknown[] {
if (!searchResults) return []
if (Array.isArray(searchResults)) return searchResults
Expand All @@ -154,16 +120,18 @@ async function fetchProfileContext(
query?: string,
signal?: AbortSignal,
): Promise<ContainerContext["profile"]> {
const profileResponse = await client.profile(
{
const profileResponse = await client.post<{
profile?: { static?: unknown[]; dynamic?: unknown[] }
searchResults?: unknown
}>("/v4/profile", {
body: {
containerTag,
include: ["static", "dynamic"],
...(query ? { q: query } : {}),
},
{ signal },
)
const profileRaw = profileResponse.profile as
| { static?: unknown[]; dynamic?: unknown[] }
| undefined
...(signal ? { signal } : {}),
})
const profileRaw = profileResponse.profile

return {
static: profileRaw?.static ?? [],
Expand Down Expand Up @@ -223,10 +191,11 @@ export async function fetchContainerContext(
export async function buildMiddlewareMemoryDebug(
containerTag: string,
conversationId: string,
memoryMode: "profile" | "query" | "full",
memoryMode: MemoryMode,
lastUserMessage: string,
middlewareConfig?: Partial<MiddlewareRuntimeConfig>,
aiSdkExtras?: {
middlewareConfig: Partial<MiddlewareRuntimeConfig> | undefined,
sdk: {
flavor: MiddlewareFlavor
includeToolCalls?: boolean
skipMemoryOnError?: boolean
},
Expand All @@ -246,7 +215,12 @@ export async function buildMiddlewareMemoryDebug(
query,
signal,
)
const selectedProfile = selectProfileForMode(profile, memoryMode)
const reconstructed = reconstructSdkMemoryBlock(
memoryMode,
profile,
sdk.flavor,
)
const selectedProfile = reconstructed.profile
const summary = summarizeProfile(selectedProfile)

return [
Expand All @@ -262,20 +236,27 @@ export async function buildMiddlewareMemoryDebug(
memoryMode,
addMemory: config.addMemory,
verbose: config.verbose,
...(aiSdkExtras?.includeToolCalls !== undefined
? { includeToolCalls: aiSdkExtras.includeToolCalls }
...(sdk.includeToolCalls !== undefined
? { includeToolCalls: sdk.includeToolCalls }
: {}),
...(aiSdkExtras?.skipMemoryOnError !== undefined
? { skipMemoryOnError: aiSdkExtras.skipMemoryOnError }
...(sdk.skipMemoryOnError !== undefined
? { skipMemoryOnError: sdk.skipMemoryOnError }
: {}),
query: query ?? null,
...summary,
},
},
{
type: "context_preview",
label: "Reconstructed context preview (not middleware capture)",
preview: buildContextPreview(selectedProfile, memoryMode, query),
label: "Reconstructed SDK-owned memory block (not middleware capture)",
preview: reconstructed.block,
detail: {
totalFacts:
summary.staticCount +
summary.dynamicCount +
summary.searchResultCount,
fullLength: reconstructed.block.length,
},
},
config.addMemory === "always"
? {
Expand All @@ -287,8 +268,8 @@ export async function buildMiddlewareMemoryDebug(
customId: conversationId,
addMemory: config.addMemory,
verbose: config.verbose,
...(aiSdkExtras?.includeToolCalls !== undefined
? { includeToolCalls: aiSdkExtras.includeToolCalls }
...(sdk.includeToolCalls !== undefined
? { includeToolCalls: sdk.includeToolCalls }
: {}),
},
}
Expand Down
Loading
Loading