Lesson 12: Advanced Parallel Extraction
This lesson showcases a powerful and efficient pattern for extracting multiple structured items from a text block in a single API call using parallel tool calls.
Code: lesson_12_parallel_extraction.mjs
The key to this pattern is defining a tool schema that represents a *single* item. We then instruct the model to call this tool for *each* item it finds. By enabling parallel calls, the model can make all these calls in one go, which we intercept to get a clean array of structured data.
// lesson_12_parallel_extraction.mjs
// Merci SDK Tutorial: Lesson 12 - Advanced Parallel Extraction
// --- IMPORTS ---
import { MerciClient, createUserMessage } from '../lib/merci.2.14.0.mjs';
import { token } from '../secret/token.mjs';
const MODEL = 'google-chat-gemini-flash-2.5';
// --- TOOL DEFINITION (FOR A SINGLE ITEM) ---
// The tool's schema represents just ONE item.
// We instruct the model to call this tool for EACH item it finds.
const singleActionItemExtractorTool = {
name: 'extract_single_action_item',
parameters: {
schema: {
type: 'object',
properties: {
task: { type: 'string', description: 'A clear description of the action to be taken.' },
assigned_to: { type: 'string', description: 'The name of the person responsible for the task.' },
due_date: { type: 'string', description: 'The deadline for the task, in YYYY-MM-DD format.' },
priority: {
type: 'string',
description: 'The priority level of the task.',
enum: ['High', 'Medium', 'Low']
}
},
required: ['task', 'assigned_to', 'due_date', 'priority']
}
},
// NOTE: No `execute` function is needed. We are only intercepting the calls.
};
async function main() {
console.log(`--- Merci SDK Lesson 12: Parallel Extraction (Model: ${MODEL}) ---`);
try {
// --- STEP 1: INITIALIZE THE CLIENT ---
console.log('[STEP 1] Initializing MerciClient...');
const client = new MerciClient({ token });
// --- STEP 2: DEFINE PROMPT AND INPUT DATA ---
console.log('[STEP 2] Preparing prompt and input data...');
const meetingMinutesContent = `
Meeting Notes: Q4 Strategy Kick-off - 2024-10-28
Attendees: David, Eve, Frank
Action Items:
- Frank is responsible for drafting the initial project specification. This is a High priority task and must be completed by 2024-11-04.
- Eve will coordinate with the design team to get the new branding assets. This is a Medium priority item with a deadline of 2024-11-08.
- David needs to book a venue for the end-of-year party. This is a Low priority task, due by 2024-11-15.
- Also, Frank needs to follow up on the Q3 budget report by the end of the week, say 2024-11-01. This is high priority.
`;
const userPrompt = `
Analyze the following meeting notes.
For each distinct action item you identify, make a parallel call to the 'extract_single_action_item' tool.
MEETING NOTES:
---
${meetingMinutesContent}
---
`;
// --- STEP 3: CONFIGURE THE CHAT SESSION ---
console.log('[STEP 3] Configuring chat session for parallel tool use...');
const chatSession = client
.chat.session(MODEL)
.withTools([singleActionItemExtractorTool])
.withParameters(builder => builder.parallelToolCalls(true));
// --- STEP 4: PREPARE THE MESSAGE PAYLOAD ---
console.log('[STEP 4] Creating the message payload...');
const messages = [createUserMessage(userPrompt)];
// --- STEP 5: EXECUTE AND INTERCEPT THE PARALLEL TOOL CALLS ---
console.log('[STEP 5] Sending request and waiting for parallel tool calls...');
let extractedItems = [];
for await (const event of chatSession.stream(messages)) {
if (event.type === 'tool_calls') {
console.log(`\n[AGENT ACTION] Model wants to make ${event.calls.length} parallel tool calls. Intercepting...`);
for (const call of event.calls) {
const argumentsObject = JSON.parse(call.arguments);
extractedItems.push(argumentsObject);
}
break; // We have our data, no need to continue.
}
}
console.log('\n[INFO] Stream finished. Response fully received.');
// --- STEP 6: DISPLAY THE FINAL EXTRACTED DATA ---
console.log('\n[STEP 6] Displaying extracted data...');
console.log('\n\n--- FINAL RESULT ---');
if (extractedItems.length > 0) {
console.log(`✅ Successfully extracted ${extractedItems.length} items in a single pass:`);
console.log(JSON.stringify(extractedItems, null, 2));
console.log('\nWe can now work with this as a native JavaScript array.');
const highPriorityCount = extractedItems.filter(item => item.priority === 'High').length;
console.log(`Found ${highPriorityCount} high-priority tasks.`);
} else {
console.error('❌ Extraction failed. The model did not attempt to call the tool.');
}
console.log('--------------------');
} catch (error) {
console.error('\n\n[FATAL ERROR] An error occurred during the operation.');
console.error(' Message:', error.message);
if (error.status) {
console.error(' API Status:', error.status);
}
if (error.details) {
console.error(' Details:', JSON.stringify(error.details, null, 2));
}
if (error.stack) {
console.error(' Stack:', error.stack);
}
console.error('\n Possible causes: Invalid token, network issues, or an API service problem.');
process.exit(1); // Exit with a non-zero code to indicate failure.
}
}
main().catch(console.error);
Expected Output
The model makes multiple, parallel calls to the same tool, one for each item it finds. We intercept these calls and parse their arguments to build a clean array of objects, all in a single API round-trip.
✅ Successfully extracted 4 items in a single pass:
[
{
"task": "Draft the initial project specification",
"assigned_to": "Frank",
"due_date": "2024-11-04",
"priority": "High"
},
{
"task": "Coordinate with the design team to get the new branding assets",
"assigned_to": "Eve",
"due_date": "2024-11-08",
"priority": "Medium"
},
{
"task": "Book a venue for the end-of-year party",
"assigned_to": "David",
"due_date": "2024-11-15",
"priority": "Low"
},
{
"task": "Follow up on the Q3 budget report",
"assigned_to": "Frank",
"due_date": "2024-11-01",
"priority": "High"
}
]
Found 2 high-priority tasks.