memory isolation fix
This commit is contained in:
@@ -64,7 +64,7 @@ async function embedEntity(entity) {
|
|||||||
return data.embedding;
|
return data.embedding;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function extractAndStoreEntities(userMessage, aiResponse) {
|
async function extractAndStoreEntities(userMessage, aiResponse, projectId=null) {
|
||||||
console.log('[entities] Extraction triggered')
|
console.log('[entities] Extraction triggered')
|
||||||
try {
|
try {
|
||||||
// Fetch existing entities to guide the model toward consistent name/type pairs
|
// Fetch existing entities to guide the model toward consistent name/type pairs
|
||||||
@@ -109,6 +109,7 @@ async function extractAndStoreEntities(userMessage, aiResponse) {
|
|||||||
name: entity.name,
|
name: entity.name,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
notes: entity.notes,
|
notes: entity.notes,
|
||||||
|
projectId: projectId ?? null,
|
||||||
}))
|
}))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.warn(`[entities] Failed to embed entity "${entity.name}":`, err.message);
|
console.warn(`[entities] Failed to embed entity "${entity.name}":`, err.message);
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function deleteSessionByExternalId(externalId) {
|
|||||||
|
|
||||||
// --Episodes --------------------------------------------------
|
// --Episodes --------------------------------------------------
|
||||||
// Creates a new episode linked to a session, with user message, AI response, optional token count, and metadata
|
// Creates a new episode linked to a session, with user message, AI response, optional token count, and metadata
|
||||||
async function createEpisode(sessionId, userMessage, aiResponse, tokenCount = null, metadata = null) {
|
async function createEpisode(sessionId, userMessage, aiResponse, tokenCount = null, metadata = null, projectId=null) {
|
||||||
const db = getDB();
|
const db = getDB();
|
||||||
|
|
||||||
// Wrap insert + session touch in a transaction — both succeed or neither does
|
// Wrap insert + session touch in a transaction — both succeed or neither does
|
||||||
@@ -128,7 +128,7 @@ async function createEpisode(sessionId, userMessage, aiResponse, tokenCount = nu
|
|||||||
}))
|
}))
|
||||||
.catch(err => console.error(`Failed to embed episode ${episode.id}:`, err.message));
|
.catch(err => console.error(`Failed to embed episode ${episode.id}:`, err.message));
|
||||||
|
|
||||||
extractAndStoreEntities(userMessage, aiResponse)
|
extractAndStoreEntities(userMessage, aiResponse, projectId)
|
||||||
.catch(err => console.error(`Failed to extract entities for episode ${episode.id}:`, err.message));
|
.catch(err => console.error(`Failed to extract entities for episode ${episode.id}:`, err.message));
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ app.delete('/sessions/by-external/:externalId', (req, res) => {
|
|||||||
/************************************* */
|
/************************************* */
|
||||||
|
|
||||||
app.post('/episodes', async (req, res) => {
|
app.post('/episodes', async (req, res) => {
|
||||||
const { sessionId, userMessage, aiResponse, tokenCount, metadata } = req.body;
|
const { sessionId, userMessage, aiResponse, tokenCount, metadata, projectId } = req.body;
|
||||||
if (!sessionId || !userMessage || !aiResponse) {
|
if (!sessionId || !userMessage || !aiResponse) {
|
||||||
return res.status(400).json({ error: 'sessionId, userMessage and aiResponse are required' });
|
return res.status(400).json({ error: 'sessionId, userMessage and aiResponse are required' });
|
||||||
}
|
}
|
||||||
const episode = await episodic.createEpisode(sessionId, userMessage, aiResponse, tokenCount, metadata);
|
const episode = await episodic.createEpisode(sessionId, userMessage, aiResponse, tokenCount, metadata, projectId);
|
||||||
|
|
||||||
console.log('[memory] create episode body:', {
|
console.log('[memory] create episode body:', {
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|||||||
@@ -107,10 +107,10 @@ async function getSemanticEpisodes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRelevantEntities(userMessage) {
|
async function getRelevantEntities(userMessage, projectId=null) {
|
||||||
try {
|
try {
|
||||||
const vector = await embedding.embed(userMessage);
|
const vector = await embedding.embed(userMessage);
|
||||||
const results = await qdrant.searchEntities(vector);
|
const results = await qdrant.searchEntities(vector, { projectId });
|
||||||
console.log(
|
console.log(
|
||||||
"[orchestration] Entity search results:",
|
"[orchestration] Entity search results:",
|
||||||
results.map((r) => ({ name: r.payload?.name, score: r.score })),
|
results.map((r) => ({ name: r.payload?.name, score: r.score })),
|
||||||
@@ -176,7 +176,7 @@ async function chat(externalId, userMessage, options = {}) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 3b. Entity Search
|
// 3b. Entity Search
|
||||||
const entities = await getRelevantEntities(userMessage);
|
const entities = await getRelevantEntities(userMessage, session.project_id ?? null);
|
||||||
|
|
||||||
// 4. Assemble prompt
|
// 4. Assemble prompt
|
||||||
const prompt = buildPrompt(
|
const prompt = buildPrompt(
|
||||||
@@ -196,6 +196,7 @@ async function chat(externalId, userMessage, options = {}) {
|
|||||||
userMessage,
|
userMessage,
|
||||||
result.text,
|
result.text,
|
||||||
(result.evalCount || 0) + (result.promptEvalCount || 0),
|
(result.evalCount || 0) + (result.promptEvalCount || 0),
|
||||||
|
session.project_id ?? null,
|
||||||
)
|
)
|
||||||
.catch((err) =>
|
.catch((err) =>
|
||||||
console.error(`[orchestration] Failed to save episode`, err.message),
|
console.error(`[orchestration] Failed to save episode`, err.message),
|
||||||
@@ -262,7 +263,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
{semanticLimit, scoreThreshold }
|
{semanticLimit, scoreThreshold }
|
||||||
);
|
);
|
||||||
|
|
||||||
const entities = await getRelevantEntities(userMessage);
|
const entities = await getRelevantEntities(userMessage, session.project_id ?? null);
|
||||||
|
|
||||||
const prompt = buildPrompt(
|
const prompt = buildPrompt(
|
||||||
recentEpisodes,
|
recentEpisodes,
|
||||||
@@ -323,7 +324,7 @@ async function chatStream(externalId, userMessage, onChunk, options = {}) {
|
|||||||
console.log("[orchestration] final streamed text length:", fullText.length);
|
console.log("[orchestration] final streamed text length:", fullText.length);
|
||||||
|
|
||||||
if (fullText.trim()) {
|
if (fullText.trim()) {
|
||||||
await memory.createEpisode(session.id, userMessage, fullText, tokenCount);
|
await memory.createEpisode(session.id, userMessage, fullText, tokenCount, session.project_id ?? null);
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[orchestration] Stream finished with no assistant text; episode not saved",
|
"[orchestration] Stream finished with no assistant text; episode not saved",
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ async function getRecentEpisodes(sessionId, limit = EPISODIC.DEFAULT_SESSIONS_LI
|
|||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createEpisode(sessionId, userMessage, aiResponse, tokenCount) {
|
async function createEpisode(sessionId, userMessage, aiResponse, tokenCount, projectId=null) {
|
||||||
const res = await fetch(`${BASE_URL}/episodes`, {
|
const res = await fetch(`${BASE_URL}/episodes`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ sessionId, userMessage, aiResponse, tokenCount })
|
body: JSON.stringify({ sessionId, userMessage, aiResponse, tokenCount, projectId })
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`Failed to create episode: ${res.status} ${res.statusText}`);
|
if (!res.ok) throw new Error(`Failed to create episode: ${res.status} ${res.statusText}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
@@ -33,9 +33,15 @@ async function searchEpisodes( vector, {limit = ORCHESTRATION.RECENT_EPISODE_LIM
|
|||||||
return data.result;
|
return data.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function searchEntities(vector, { limit = 5, scoreThreshold = 0.6 } = {}) {
|
async function searchEntities(vector, { limit = ORCHESTRATION.ENTITIES_LIMIT, scoreThreshold = ORCHESTRATION.ENTITIES_THRESHOLD, projectId = undefined } = {}) {
|
||||||
const body = { vector, limit, score_threshold: scoreThreshold, with_payload: true };
|
const body = { vector, limit, score_threshold: scoreThreshold, with_payload: true };
|
||||||
|
|
||||||
|
if (projectId !== undefined) {
|
||||||
|
body.filter = {
|
||||||
|
must: [{ key: 'projectId', match: { value: projectId ?? null } }]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${BASE_URL}/collections/${COLLECTIONS.ENTITIES}/points/search`,
|
`${BASE_URL}/collections/${COLLECTIONS.ENTITIES}/points/search`,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const ORCHESTRATION = {
|
|||||||
RECENT_EPISODE_LIMIT: 5,
|
RECENT_EPISODE_LIMIT: 5,
|
||||||
SEMANTIC_LIMIT: 5,
|
SEMANTIC_LIMIT: 5,
|
||||||
SCORE_THRESHOLD: 0.75,
|
SCORE_THRESHOLD: 0.75,
|
||||||
|
ENTITIES_LIMIT: 5,
|
||||||
|
ENTITIES_THRESHOLD: 0.75,
|
||||||
TEMPERATURE: 0.7,
|
TEMPERATURE: 0.7,
|
||||||
CORS_ORIGIN: 'http://localhost:5173',
|
CORS_ORIGIN: 'http://localhost:5173',
|
||||||
SYSTEM_PROMPT: `You are a helpful, context-aware AI assistant. You have access to memories of past conversations with the user. Use them to provide consistent, personalised responses.`
|
SYSTEM_PROMPT: `You are a helpful, context-aware AI assistant. You have access to memories of past conversations with the user. Use them to provide consistent, personalised responses.`
|
||||||
|
|||||||
Reference in New Issue
Block a user