lexguard-backend / src /services /agent5Chat.js
github-actions[bot]
Deploy to Hugging Face
b921752
Raw
History Blame Contribute Delete
3.38 kB
/**
* Agent 5 - Contract Chat
*
* Provides an interactive Q&A interface for a specific contract.
*/
const { callLLM } = require('./aiClient');
const Clause = require('../models/Clause');
const Contract = require('../models/Contract');
const SYSTEM_PROMPT = `You are LexGuard Chat (Agent 5), a highly intelligent legal assistant.
You are helping the user understand their uploaded contract.
You are provided with the FULL TEXT of the contract clauses below, along with the user's question.
### Rules:
1. Provide accurate, helpful, and concise answers based strictly on the provided contract text.
2. If the answer is not in the contract, say so clearly. Do not hallucinate terms.
3. You are not a lawyer. Provide a standard disclaimer if the user asks for formal legal advice.
4. Format your response in clean Markdown.
5. YOU MUST output valid JSON only in the following format:
{
"response": "Your markdown formatted answer here..."
}
`;
async function chatWithContract(contractId, userMessage) {
const { generateEmbedding } = require('./embeddingService');
const targetId = typeof contractId === 'string' ? new mongoose.Types.ObjectId(contractId) : contractId;
let contextText = '';
try {
// Generate embedding for the user's question
const queryVector = await generateEmbedding(userMessage);
// Perform vector search to find the top 5 most relevant clauses
// Note: Requires an Atlas Vector Search index named "vector_index" on the "clauses" collection
const relevantClauses = await Clause.aggregate([
{
$vectorSearch: {
index: "vector_index",
path: "embedding",
queryVector: queryVector,
numCandidates: 50,
limit: 5,
filter: { contractId: targetId }
}
},
{
$project: {
segmentIndex: 1,
clause_type: 1,
rawText: 1,
risk_level: 1,
plain_language_explanation: 1,
score: { $meta: "vectorSearchScore" }
}
}
]);
if (relevantClauses && relevantClauses.length > 0) {
contextText = relevantClauses.map(c =>
`[Clause ${c.segmentIndex + 1} - Type: ${c.clause_type || 'Unknown'} - Risk: ${c.risk_level || 'Unknown'}]\n${c.rawText}\nExplanation: ${c.plain_language_explanation || ''}`
).join('\n\n');
} else {
contextText = 'No highly relevant clauses found for this query.';
}
} catch (err) {
console.warn('⚠️ Vector search failed, falling back to full text context.', err.message);
// Fallback: use first 20 clauses to avoid overflowing token limit
const clauses = await Clause.find({ contractId }).sort({ segmentIndex: 1 }).limit(20);
if (!clauses || clauses.length === 0) {
throw new Error('No clauses found for this contract. Please ensure it has been analyzed.');
}
contextText = clauses.map(c => `[Clause ${c.segmentIndex + 1} - Type: ${c.clause_type || 'Unknown'}]\n${c.rawText}`).join('\n\n');
}
const userContent = JSON.stringify({
contract_context: contextText,
user_question: userMessage
});
const resp = await callLLM({
systemPrompt: SYSTEM_PROMPT,
userContent,
jsonMode: true,
temperature: 0.3,
maxTokens: 2048,
});
return resp.response || "I'm sorry, I couldn't generate a response.";
}
module.exports = { chatWithContract };