[FTCHAT] Agent engine - context builder with memory and toolchain

This commit is contained in:
bingshuo 2026-05-18 19:43:50 +08:00
parent 3a616a7b26
commit d258f9a85d

View File

@ -0,0 +1,140 @@
/**
* Agent engine: context builder + toolchain orchestrator
*
* Builds the message context for vLLM inference:
* - No system prompt (model trained on real conversations)
* - Previous conversation memory (mother tongue imprint)
* - Tool results (Notion, repo) injected as user/assistant messages
* - Auto conversation rotation at threshold
*/
'use strict';
const vllm = require('./vllm-proxy');
const sessionStore = require('./session-store');
const memoryAgent = require('./memory-agent');
const toolRegistry = require('./tool-registry');
const MAX_MESSAGES_BEFORE_ROTATE = 50;
const MAX_CONTEXT_TOKENS = 3072;
/**
* Build the full message context for vLLM
*
* Context structure:
* [
* {role:'user', content:'[Mother Tongue Imprint from last session]'},
* {role:'assistant', content:'[Understood. Continuing from where we left off.]'},
* ...previous messages,
* {role:'user', content:'current message'}
* ]
*
* NO system prompt - the model was trained on real conversations without system prompts.
*/
function buildContext(userHash, slotIndex, newMessage, conversationHistory, options = {}) {
const messages = [];
// 1. Cross-session memory imprint (if exists)
const imprint = sessionStore.getLatestImprint(userHash);
if (imprint) {
messages.push({
role: 'user',
content: '[Previous conversation memory]\n' + imprint
});
messages.push({
role: 'assistant',
content: '[I understand. I will continue based on our previous conversation.]'
});
}
// 2. Current conversation history
const history = conversationHistory || [];
for (const msg of history) {
if (msg.role === 'system') continue; // Never inject system messages
messages.push({
role: msg.role,
content: msg.content || ''
});
}
// 3. Current user message
messages.push({
role: 'user',
content: newMessage || ''
});
return messages;
}
/**
* Process a chat message through the agent pipeline:
* 1. Build context (memory + history)
* 2. Send to vLLM
* 3. Check if model requested a tool call
* 4. If tool call: execute tool, inject result, re-request
* 5. Return final response
*/
async function processMessage(userHash, slotIndex, message, conversationHistory = []) {
const context = buildContext(userHash, slotIndex, message, conversationHistory);
// Send to vLLM
let response = await vllm.chatOnce(context, {
maxTokens: 1024,
temperature: 0.7
});
// Check for tool calls
const toolCall = toolRegistry.parseToolCall(response);
if (toolCall) {
context.push({ role: 'assistant', content: response });
try {
const toolResult = await toolRegistry.executeToolCall(toolCall);
context.push({ role: 'user', content: '[Tool Result]\n' + JSON.stringify(toolResult, null, 2) });
// Re-request with tool result
response = await vllm.chatOnce(context, {
maxTokens: 1024,
temperature: 0.7
});
} catch (err) {
context.push({ role: 'user', content: '[Tool Error]\n' + err.message });
response = await vllm.chatOnce(context, {
maxTokens: 512,
temperature: 0.7
});
}
}
return {
response,
shouldRotate: conversationHistory.length >= MAX_MESSAGES_BEFORE_ROTATE
};
}
/**
* Stream a chat message through the pipeline, writing SSE to res
*/
async function streamProcessMessage(userHash, slotIndex, message, conversationHistory, res) {
const context = buildContext(userHash, slotIndex, message, conversationHistory);
// Stream from vLLM
vllm.pipeChat(context, res, {
maxTokens: 1024,
temperature: 0.7
});
return {
shouldRotate: conversationHistory.length >= MAX_MESSAGES_BEFORE_ROTATE
};
}
function getMaxMessages() {
return MAX_MESSAGES_BEFORE_ROTATE;
}
module.exports = {
buildContext,
processMessage,
streamProcessMessage,
getMaxMessages
};