[FTCHAT] Agent engine - context builder with memory and toolchain
This commit is contained in:
parent
3a616a7b26
commit
d258f9a85d
140
server/ftchat/services/agent-engine.js
Normal file
140
server/ftchat/services/agent-engine.js
Normal 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
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user