morpheus-cli 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/link.js +109 -1
- package/dist/runtime/oracle.js +7 -0
- package/package.json +1 -1
package/dist/runtime/link.js
CHANGED
|
@@ -144,7 +144,102 @@ export class Link {
|
|
|
144
144
|
return `Found ${results.length} passages in "${doc.filename}":\n\n${formatted}`;
|
|
145
145
|
},
|
|
146
146
|
});
|
|
147
|
-
|
|
147
|
+
// Tool: Summarize entire document via LLM
|
|
148
|
+
const summarizeDocumentTool = new DynamicStructuredTool({
|
|
149
|
+
name: 'link_summarize_document',
|
|
150
|
+
description: 'Summarize an entire indexed document using the LLM. Use this when user wants a summary of a whole document (e.g., "resuma o contrato", "give me a summary of the report"). Returns a concise summary generated by the LLM.',
|
|
151
|
+
schema: z.object({
|
|
152
|
+
document_id: z.string().describe('The document ID to summarize (get this from link_list_documents)'),
|
|
153
|
+
max_chunks: z.number().optional().describe('Maximum number of chunks to include in summary (default: 50)'),
|
|
154
|
+
}),
|
|
155
|
+
func: async ({ document_id, max_chunks }) => {
|
|
156
|
+
const doc = repository.getDocument(document_id);
|
|
157
|
+
if (!doc) {
|
|
158
|
+
return `Document not found: ${document_id}`;
|
|
159
|
+
}
|
|
160
|
+
const chunks = repository.getChunksByDocument(document_id);
|
|
161
|
+
if (chunks.length === 0) {
|
|
162
|
+
return `Document "${doc.filename}" has no indexed chunks.`;
|
|
163
|
+
}
|
|
164
|
+
const limit = max_chunks ?? 50;
|
|
165
|
+
const chunksToSummarize = chunks.slice(0, limit);
|
|
166
|
+
const content = chunksToSummarize.map(c => c.content).join('\n\n---\n\n');
|
|
167
|
+
// Return the content for LLM to summarize - the ReactAgent will handle the summarization
|
|
168
|
+
return `Document: ${doc.filename}\nTotal chunks: ${chunks.length}\nChunks to summarize: ${chunksToSummarize.length}\n\nContent:\n${content}`;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
// Tool: Summarize specific chunk via LLM
|
|
172
|
+
const summarizeChunkTool = new DynamicStructuredTool({
|
|
173
|
+
name: 'link_summarize_chunk',
|
|
174
|
+
description: 'Summarize a specific chunk from a document. Use this when user wants to summarize a particular section (e.g., "resuma o chunk 5", "summarize section 3").',
|
|
175
|
+
schema: z.object({
|
|
176
|
+
document_id: z.string().describe('The document ID (get this from link_list_documents)'),
|
|
177
|
+
position: z.number().describe('The chunk position to summarize (1-based)'),
|
|
178
|
+
}),
|
|
179
|
+
func: async ({ document_id, position }) => {
|
|
180
|
+
const doc = repository.getDocument(document_id);
|
|
181
|
+
if (!doc) {
|
|
182
|
+
return `Document not found: ${document_id}`;
|
|
183
|
+
}
|
|
184
|
+
const chunks = repository.getChunksByDocument(document_id);
|
|
185
|
+
const chunk = chunks.find(c => c.position === position);
|
|
186
|
+
if (!chunk) {
|
|
187
|
+
return `Chunk not found: position ${position}. Document "${doc.filename}" has ${chunks.length} chunks.`;
|
|
188
|
+
}
|
|
189
|
+
return `Document: ${doc.filename}\nChunk position: ${position}\n\nContent:\n${chunk.content}`;
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
// Tool: Extract key points from document
|
|
193
|
+
const extractKeyPointsTool = new DynamicStructuredTool({
|
|
194
|
+
name: 'link_extract_key_points',
|
|
195
|
+
description: 'Extract key points from a document. Use this when user wants to know the main points or key takeaways from a document (e.g., "quais os pontos principais do contrato?", "extract key points from the report").',
|
|
196
|
+
schema: z.object({
|
|
197
|
+
document_id: z.string().describe('The document ID to extract key points from (get this from link_list_documents)'),
|
|
198
|
+
max_chunks: z.number().optional().describe('Maximum number of chunks to analyze (default: 50)'),
|
|
199
|
+
}),
|
|
200
|
+
func: async ({ document_id, max_chunks }) => {
|
|
201
|
+
const doc = repository.getDocument(document_id);
|
|
202
|
+
if (!doc) {
|
|
203
|
+
return `Document not found: ${document_id}`;
|
|
204
|
+
}
|
|
205
|
+
const chunks = repository.getChunksByDocument(document_id);
|
|
206
|
+
if (chunks.length === 0) {
|
|
207
|
+
return `Document "${doc.filename}" has no indexed chunks.`;
|
|
208
|
+
}
|
|
209
|
+
const limit = max_chunks ?? 1000;
|
|
210
|
+
const chunksToAnalyze = chunks.slice(0, limit);
|
|
211
|
+
const content = chunksToAnalyze.map(c => c.content).join('\n\n---\n\n');
|
|
212
|
+
return `Document: ${doc.filename}\nTotal chunks: ${chunks.length}\nChunks to analyze: ${chunksToAnalyze.length}\n\nContent:\n${content}`;
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
// Tool: Find differences between two documents
|
|
216
|
+
const findDifferencesTool = new DynamicStructuredTool({
|
|
217
|
+
name: 'link_find_differences',
|
|
218
|
+
description: 'Compare two documents and identify differences. Use this when user wants to compare documents (e.g., "compare os dois contratos", "what is different between doc A and doc B").',
|
|
219
|
+
schema: z.object({
|
|
220
|
+
document_id_a: z.string().describe('First document ID to compare'),
|
|
221
|
+
document_id_b: z.string().describe('Second document ID to compare'),
|
|
222
|
+
}),
|
|
223
|
+
func: async ({ document_id_a, document_id_b }) => {
|
|
224
|
+
const docA = repository.getDocument(document_id_a);
|
|
225
|
+
const docB = repository.getDocument(document_id_b);
|
|
226
|
+
if (!docA) {
|
|
227
|
+
return `Document not found: ${document_id_a}`;
|
|
228
|
+
}
|
|
229
|
+
if (!docB) {
|
|
230
|
+
return `Document not found: ${document_id_b}`;
|
|
231
|
+
}
|
|
232
|
+
if (document_id_a === document_id_b) {
|
|
233
|
+
return 'Os documentos são idênticos (mesmo documento informado duas vezes).';
|
|
234
|
+
}
|
|
235
|
+
const chunksA = repository.getChunksByDocument(document_id_a);
|
|
236
|
+
const chunksB = repository.getChunksByDocument(document_id_b);
|
|
237
|
+
const contentA = chunksA.map(c => c.content).join('\n\n---\n\n');
|
|
238
|
+
const contentB = chunksB.map(c => c.content).join('\n\n---\n\n');
|
|
239
|
+
return `Document A: ${docA.filename} (${chunksA.length} chunks)\nDocument B: ${docB.filename} (${chunksB.length} chunks)\n\n--- Document A Content ---\n${contentA}\n\n--- Document B Content ---\n${contentB}`;
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
return [listDocumentsTool, searchTool, searchInDocumentTool, summarizeDocumentTool, summarizeChunkTool, extractKeyPointsTool, findDifferencesTool];
|
|
148
243
|
}
|
|
149
244
|
async initialize() {
|
|
150
245
|
this.repository.initialize();
|
|
@@ -218,6 +313,19 @@ Rules:
|
|
|
218
313
|
- When the user asks a general question without referencing a specific document:
|
|
219
314
|
- Use **link_search_documents** for a broad search across all documents.
|
|
220
315
|
- When unsure which document contains the answer, start with **link_search_documents**, then narrow down with **link_search_in_document** if results point to a specific file.
|
|
316
|
+
- When user asks to summarize, extract key points, or compare documents:
|
|
317
|
+
- Use **link_summarize_document**, **link_extract_key_points**, or **link_find_differences** respectively.
|
|
318
|
+
|
|
319
|
+
## Source Citation with Scores
|
|
320
|
+
AT THE END of your response, you MUST include a "Fontes consultadas:" (Sources consulted) section listing all documents used with their final scores.
|
|
321
|
+
Format:
|
|
322
|
+
- <filename> (<document_id>): score <score_value>
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
- readme.md (abc-123): score 0.92
|
|
326
|
+
- contract.pdf (xyz-789): score 0.85
|
|
327
|
+
|
|
328
|
+
If no documents were used, omit this section.
|
|
221
329
|
|
|
222
330
|
${context ? `Context:\n${context}` : ''}
|
|
223
331
|
`);
|
package/dist/runtime/oracle.js
CHANGED
|
@@ -388,6 +388,13 @@ When delegating to Link:
|
|
|
388
388
|
- Sati context may be included ONLY as a separate "context for interpretation" section, clearly separated from the search objective, so Link can use it to interpret results — never to filter or bias the search itself.
|
|
389
389
|
- Constraints such as response length, specific documents to search, or resources to avoid may be included.
|
|
390
390
|
|
|
391
|
+
## Reponses rules for Link delegations:
|
|
392
|
+
- When Link returns a response, ALWAYS preserve the full response from Link. NEVER summarize, truncate, or rephrase the content returned by Link.
|
|
393
|
+
- If the user asks for a summary (e.g., "resuma o documento", "me dá um resumo"), use the Link's response AS-IS - do not summarize again.
|
|
394
|
+
- Keep the "Fontes consultadas:" (Sources consulted) section at the end of Link's response - this is important metadata.
|
|
395
|
+
- NEVER add, invent, or remove information from Link's response. The response from Link is the source of truth.
|
|
396
|
+
- If the user request is purely informational (e.g., "qual é minha formação acadêmica?"), you MAY simplify the response but MUST preserve all key facts from Link's answer.
|
|
397
|
+
- Do NOT create a task or delegate to Neo for document-related questions - just return Link's response directly.
|
|
391
398
|
|
|
392
399
|
---------------------
|
|
393
400
|
SKILLS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morpheus-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Morpheus is a local AI agent for developers, running as a CLI daemon that connects to LLMs, local tools, and MCPs, enabling interaction via Terminal, Telegram, and Discord. Inspired by the character Morpheus from *The Matrix*, the project acts as an intelligent orchestrator, bridging the gap between the developer and complex systems.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"morpheus": "./bin/morpheus.js"
|