[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