Add integration guide for Report Generation API, detailing endpoints for generating full, markdown, and JSON reports. Update agent executor and triage routes to track tool executions and guidelines count for report generation, enhancing logging for better traceability. Ensure all tool executions are logged for report generation success.
Browse files- REPORT_API_INTEGRATION.md +60 -0
- src/agent/agent-executor.ts +21 -0
- src/routes/triage.route.ts +34 -12
- src/services/tool-execution-tracker.service.ts +6 -1
REPORT_API_INTEGRATION.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Report Generation API - Integration Guide
|
| 2 |
+
|
| 3 |
+
## Tổng quan
|
| 4 |
+
API tạo báo cáo tổng hợp đầy đủ từ conversation session, bao gồm tất cả thông tin từ tools (CV top 3, RAG guidelines, triage results, hospital suggestions) mà response message thường bỏ qua.
|
| 5 |
+
|
| 6 |
+
## Endpoints
|
| 7 |
+
|
| 8 |
+
### 1. Generate/Get Full Report
|
| 9 |
+
```
|
| 10 |
+
GET /api/reports/:session_id?type=full|summary|tools_only
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
**Response:**
|
| 14 |
+
```json
|
| 15 |
+
{
|
| 16 |
+
"session_id": "uuid",
|
| 17 |
+
"report_type": "full",
|
| 18 |
+
"generated_at": "2024-01-01T00:00:00Z",
|
| 19 |
+
"report": {
|
| 20 |
+
"report_content": { /* Full structured data */ },
|
| 21 |
+
"report_markdown": "# BÁO CÁO TỔNG HỢP..."
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 2. Get Markdown Only
|
| 27 |
+
```
|
| 28 |
+
GET /api/reports/:session_id/markdown
|
| 29 |
+
```
|
| 30 |
+
Trả về markdown report đã format sẵn, dễ hiển thị.
|
| 31 |
+
|
| 32 |
+
### 3. Get JSON Only
|
| 33 |
+
```
|
| 34 |
+
GET /api/reports/:session_id/json
|
| 35 |
+
```
|
| 36 |
+
Trả về structured data (conversation timeline, tool executions, summary).
|
| 37 |
+
|
| 38 |
+
## Cách sử dụng
|
| 39 |
+
|
| 40 |
+
```javascript
|
| 41 |
+
// Sau khi có session_id từ /api/health-check
|
| 42 |
+
const sessionId = "abc-123-def";
|
| 43 |
+
|
| 44 |
+
// Generate full report
|
| 45 |
+
const response = await fetch(`/api/reports/${sessionId}?type=full`);
|
| 46 |
+
const { report } = await response.json();
|
| 47 |
+
|
| 48 |
+
// Hiển thị markdown
|
| 49 |
+
console.log(report.report_markdown);
|
| 50 |
+
|
| 51 |
+
// Hoặc parse structured data
|
| 52 |
+
const { conversation_timeline, tool_executions, summary } = report.report_content;
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
## Lưu ý
|
| 56 |
+
- Report được cache, gọi lại sẽ trả về cached version
|
| 57 |
+
- `type=full`: Báo cáo đầy đủ (default)
|
| 58 |
+
- `type=summary`: Chỉ tóm tắt
|
| 59 |
+
- `type=tools_only`: Chỉ tool executions
|
| 60 |
+
|
src/agent/agent-executor.ts
CHANGED
|
@@ -369,6 +369,8 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 369 |
logger.info(`[AGENT] Calling MCP RAG - searchGuidelines...`);
|
| 370 |
const guidelines = await this.ragService.searchGuidelines(guidelineInput);
|
| 371 |
logger.info(`[AGENT] Retrieved ${guidelines.length} guideline snippets from RAG`);
|
|
|
|
|
|
|
| 372 |
|
| 373 |
// Step 4: Use LLM to synthesize final response
|
| 374 |
logger.info('Step 4: Synthesizing final response with LLM...');
|
|
@@ -384,6 +386,9 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 384 |
guidelines,
|
| 385 |
conversationContext
|
| 386 |
);
|
|
|
|
|
|
|
|
|
|
| 387 |
|
| 388 |
// Step 5: Find best matching hospital if emergency/urgent and location provided
|
| 389 |
// This tool is called LAST in the agent workflow
|
|
@@ -394,6 +399,7 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 394 |
|
| 395 |
if ((triageResult.triage === 'emergency' || triageResult.triage === 'urgent') && location) {
|
| 396 |
logger.info(`[AGENT] Step 5: Finding best matching hospital (emergency/urgent case)${condition ? ` for condition: ${condition}` : ''}...`);
|
|
|
|
| 397 |
try {
|
| 398 |
const bestHospital = await this.mapsService.findBestMatchingHospital(
|
| 399 |
location,
|
|
@@ -402,20 +408,24 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 402 |
);
|
| 403 |
if (bestHospital) {
|
| 404 |
logger.info(`[AGENT] Found best matching hospital: ${bestHospital.name} (${bestHospital.distance_km}km away${bestHospital.specialty_score ? `, specialty match: ${bestHospital.specialty_score.toFixed(2)}` : ''})`);
|
|
|
|
| 405 |
return {
|
| 406 |
...finalResult,
|
| 407 |
nearest_clinic: bestHospital
|
| 408 |
};
|
| 409 |
} else {
|
| 410 |
logger.warn('[AGENT] No hospital found nearby');
|
|
|
|
| 411 |
}
|
| 412 |
} catch (error) {
|
| 413 |
logger.error({ error }, '[AGENT] Failed to find best matching hospital');
|
|
|
|
| 414 |
// Continue without hospital info
|
| 415 |
}
|
| 416 |
} else if (location && this.shouldSuggestHospital(userText)) {
|
| 417 |
// Also suggest hospital if user explicitly requests it
|
| 418 |
logger.info(`[AGENT] Step 5: Finding best matching hospital (user requested)${condition ? ` for condition: ${condition}` : ''}...`);
|
|
|
|
| 419 |
try {
|
| 420 |
const bestHospital = await this.mapsService.findBestMatchingHospital(
|
| 421 |
location,
|
|
@@ -424,6 +434,7 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 424 |
);
|
| 425 |
if (bestHospital) {
|
| 426 |
logger.info(`[AGENT] Found best matching hospital: ${bestHospital.name} (${bestHospital.distance_km}km away${bestHospital.specialty_score ? `, specialty match: ${bestHospital.specialty_score.toFixed(2)}` : ''})`);
|
|
|
|
| 427 |
return {
|
| 428 |
...finalResult,
|
| 429 |
nearest_clinic: bestHospital
|
|
@@ -431,6 +442,13 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 431 |
}
|
| 432 |
} catch (error) {
|
| 433 |
logger.error({ error }, '[AGENT] Failed to find best matching hospital');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
}
|
| 435 |
}
|
| 436 |
|
|
@@ -500,6 +518,9 @@ Ví dụ format markdown NGẮN GỌN:
|
|
| 500 |
guidelines,
|
| 501 |
conversationContext
|
| 502 |
);
|
|
|
|
|
|
|
|
|
|
| 503 |
|
| 504 |
// Step 4: Find best matching hospital if emergency/urgent and location provided
|
| 505 |
// This tool is called LAST in the agent workflow
|
|
|
|
| 369 |
logger.info(`[AGENT] Calling MCP RAG - searchGuidelines...`);
|
| 370 |
const guidelines = await this.ragService.searchGuidelines(guidelineInput);
|
| 371 |
logger.info(`[AGENT] Retrieved ${guidelines.length} guideline snippets from RAG`);
|
| 372 |
+
// Store guidelines count for report generation
|
| 373 |
+
(guidelineInput as any).guidelines_count = guidelines.length;
|
| 374 |
|
| 375 |
// Step 4: Use LLM to synthesize final response
|
| 376 |
logger.info('Step 4: Synthesizing final response with LLM...');
|
|
|
|
| 386 |
guidelines,
|
| 387 |
conversationContext
|
| 388 |
);
|
| 389 |
+
|
| 390 |
+
// Attach guidelines count to result for report generation
|
| 391 |
+
(finalResult as any).guidelines_count = guidelines.length;
|
| 392 |
|
| 393 |
// Step 5: Find best matching hospital if emergency/urgent and location provided
|
| 394 |
// This tool is called LAST in the agent workflow
|
|
|
|
| 399 |
|
| 400 |
if ((triageResult.triage === 'emergency' || triageResult.triage === 'urgent') && location) {
|
| 401 |
logger.info(`[AGENT] Step 5: Finding best matching hospital (emergency/urgent case)${condition ? ` for condition: ${condition}` : ''}...`);
|
| 402 |
+
logger.info('[REPORT] Hospital tool (MCP) will be executed for emergency/urgent case');
|
| 403 |
try {
|
| 404 |
const bestHospital = await this.mapsService.findBestMatchingHospital(
|
| 405 |
location,
|
|
|
|
| 408 |
);
|
| 409 |
if (bestHospital) {
|
| 410 |
logger.info(`[AGENT] Found best matching hospital: ${bestHospital.name} (${bestHospital.distance_km}km away${bestHospital.specialty_score ? `, specialty match: ${bestHospital.specialty_score.toFixed(2)}` : ''})`);
|
| 411 |
+
logger.info(`[REPORT] ✓ Hospital tool (MCP) executed successfully: ${bestHospital.name}`);
|
| 412 |
return {
|
| 413 |
...finalResult,
|
| 414 |
nearest_clinic: bestHospital
|
| 415 |
};
|
| 416 |
} else {
|
| 417 |
logger.warn('[AGENT] No hospital found nearby');
|
| 418 |
+
logger.info('[REPORT] Hospital tool (MCP) executed but no hospital found');
|
| 419 |
}
|
| 420 |
} catch (error) {
|
| 421 |
logger.error({ error }, '[AGENT] Failed to find best matching hospital');
|
| 422 |
+
logger.error('[REPORT] Hospital tool (MCP) execution failed');
|
| 423 |
// Continue without hospital info
|
| 424 |
}
|
| 425 |
} else if (location && this.shouldSuggestHospital(userText)) {
|
| 426 |
// Also suggest hospital if user explicitly requests it
|
| 427 |
logger.info(`[AGENT] Step 5: Finding best matching hospital (user requested)${condition ? ` for condition: ${condition}` : ''}...`);
|
| 428 |
+
logger.info('[REPORT] Hospital tool (MCP) will be executed (user explicitly requested)');
|
| 429 |
try {
|
| 430 |
const bestHospital = await this.mapsService.findBestMatchingHospital(
|
| 431 |
location,
|
|
|
|
| 434 |
);
|
| 435 |
if (bestHospital) {
|
| 436 |
logger.info(`[AGENT] Found best matching hospital: ${bestHospital.name} (${bestHospital.distance_km}km away${bestHospital.specialty_score ? `, specialty match: ${bestHospital.specialty_score.toFixed(2)}` : ''})`);
|
| 437 |
+
logger.info(`[REPORT] ✓ Hospital tool (MCP) executed successfully: ${bestHospital.name}`);
|
| 438 |
return {
|
| 439 |
...finalResult,
|
| 440 |
nearest_clinic: bestHospital
|
|
|
|
| 442 |
}
|
| 443 |
} catch (error) {
|
| 444 |
logger.error({ error }, '[AGENT] Failed to find best matching hospital');
|
| 445 |
+
logger.error('[REPORT] Hospital tool (MCP) execution failed');
|
| 446 |
+
}
|
| 447 |
+
} else {
|
| 448 |
+
if (location) {
|
| 449 |
+
logger.info(`[REPORT] Hospital tool (MCP) skipped: triage_level=${triageResult.triage} (only called for emergency/urgent or explicit request)`);
|
| 450 |
+
} else {
|
| 451 |
+
logger.info('[REPORT] Hospital tool (MCP) skipped: no location provided');
|
| 452 |
}
|
| 453 |
}
|
| 454 |
|
|
|
|
| 518 |
guidelines,
|
| 519 |
conversationContext
|
| 520 |
);
|
| 521 |
+
|
| 522 |
+
// Attach guidelines count to result for report generation
|
| 523 |
+
(finalResult as any).guidelines_count = guidelines.length;
|
| 524 |
|
| 525 |
// Step 4: Find best matching hospital if emergency/urgent and location provided
|
| 526 |
// This tool is called LAST in the agent workflow
|
src/routes/triage.route.ts
CHANGED
|
@@ -260,18 +260,27 @@ export async function triageRoutes(
|
|
| 260 |
// Continue even if saving fails
|
| 261 |
}
|
| 262 |
|
| 263 |
-
// Track tool executions (non-blocking)
|
| 264 |
try {
|
|
|
|
|
|
|
| 265 |
// Track CV execution if applicable
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
// Track Triage Rules execution
|
|
|
|
| 275 |
await ToolTrackingHelper.trackTriageRulesExecution(
|
| 276 |
toolTracker,
|
| 277 |
activeSessionId,
|
|
@@ -280,9 +289,11 @@ export async function triageRoutes(
|
|
| 280 |
normalizedText || 'Image analysis',
|
| 281 |
Math.floor(totalExecutionTime * 0.2) // Estimate 20% of time
|
| 282 |
);
|
|
|
|
| 283 |
|
| 284 |
-
// Track RAG execution
|
| 285 |
-
const guidelinesCount = (triageResult as any).guidelines_count || 3; //
|
|
|
|
| 286 |
await ToolTrackingHelper.trackRAGExecution(
|
| 287 |
toolTracker,
|
| 288 |
activeSessionId,
|
|
@@ -292,10 +303,12 @@ export async function triageRoutes(
|
|
| 292 |
Math.floor(totalExecutionTime * 0.3), // Estimate 30% of time
|
| 293 |
guidelinesCount
|
| 294 |
);
|
|
|
|
| 295 |
|
| 296 |
-
// Track Maps execution if hospital was found
|
| 297 |
const nearestClinic = (triageResult as any).nearest_clinic;
|
| 298 |
if (nearestClinic) {
|
|
|
|
| 299 |
const condition = triageResult.suspected_conditions?.[0]?.name;
|
| 300 |
await ToolTrackingHelper.trackMapsExecution(
|
| 301 |
toolTracker,
|
|
@@ -305,9 +318,18 @@ export async function triageRoutes(
|
|
| 305 |
condition,
|
| 306 |
Math.floor(totalExecutionTime * 0.2) // Estimate 20% of time
|
| 307 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
}
|
|
|
|
|
|
|
| 309 |
} catch (error) {
|
| 310 |
-
logger.error({ error }, 'Failed to track tool executions');
|
| 311 |
// Continue even if tracking fails
|
| 312 |
}
|
| 313 |
|
|
|
|
| 260 |
// Continue even if saving fails
|
| 261 |
}
|
| 262 |
|
| 263 |
+
// Track tool executions for Report Generation (non-blocking)
|
| 264 |
try {
|
| 265 |
+
logger.info('[REPORT] Starting tool execution tracking for report generation...');
|
| 266 |
+
|
| 267 |
// Track CV execution if applicable
|
| 268 |
+
if (triageResult.cv_findings.model_used !== 'none') {
|
| 269 |
+
logger.info('[REPORT] Tracking CV tool execution...');
|
| 270 |
+
await ToolTrackingHelper.trackCVExecution(
|
| 271 |
+
toolTracker,
|
| 272 |
+
activeSessionId,
|
| 273 |
+
userMessage.id,
|
| 274 |
+
triageResult,
|
| 275 |
+
Math.floor(totalExecutionTime * 0.3) // Estimate 30% of time for CV
|
| 276 |
+
);
|
| 277 |
+
logger.info(`[REPORT] ✓ CV tool tracked: ${triageResult.cv_findings.model_used}`);
|
| 278 |
+
} else {
|
| 279 |
+
logger.info('[REPORT] CV tool not executed (no image or model_used=none)');
|
| 280 |
+
}
|
| 281 |
|
| 282 |
// Track Triage Rules execution
|
| 283 |
+
logger.info('[REPORT] Tracking Triage Rules execution...');
|
| 284 |
await ToolTrackingHelper.trackTriageRulesExecution(
|
| 285 |
toolTracker,
|
| 286 |
activeSessionId,
|
|
|
|
| 289 |
normalizedText || 'Image analysis',
|
| 290 |
Math.floor(totalExecutionTime * 0.2) // Estimate 20% of time
|
| 291 |
);
|
| 292 |
+
logger.info(`[REPORT] ✓ Triage Rules tracked: level=${triageResult.triage_level}`);
|
| 293 |
|
| 294 |
+
// Track RAG execution - extract actual guidelines count from agent result
|
| 295 |
+
const guidelinesCount = (triageResult as any).guidelines_count || 3; // Captured from agent execution
|
| 296 |
+
logger.info('[REPORT] Tracking RAG/Guidelines execution...');
|
| 297 |
await ToolTrackingHelper.trackRAGExecution(
|
| 298 |
toolTracker,
|
| 299 |
activeSessionId,
|
|
|
|
| 303 |
Math.floor(totalExecutionTime * 0.3), // Estimate 30% of time
|
| 304 |
guidelinesCount
|
| 305 |
);
|
| 306 |
+
logger.info(`[REPORT] ✓ RAG tool tracked: ${guidelinesCount} guidelines retrieved`);
|
| 307 |
|
| 308 |
+
// Track Maps/Hospital execution if hospital was found
|
| 309 |
const nearestClinic = (triageResult as any).nearest_clinic;
|
| 310 |
if (nearestClinic) {
|
| 311 |
+
logger.info('[REPORT] Tracking Hospital/Maps tool execution...');
|
| 312 |
const condition = triageResult.suspected_conditions?.[0]?.name;
|
| 313 |
await ToolTrackingHelper.trackMapsExecution(
|
| 314 |
toolTracker,
|
|
|
|
| 318 |
condition,
|
| 319 |
Math.floor(totalExecutionTime * 0.2) // Estimate 20% of time
|
| 320 |
);
|
| 321 |
+
logger.info(`[REPORT] ✓ Hospital tool tracked: ${nearestClinic.name} (${nearestClinic.distance_km}km)`);
|
| 322 |
+
} else {
|
| 323 |
+
if (location) {
|
| 324 |
+
logger.info(`[REPORT] Hospital tool not executed: triage_level=${triageResult.triage_level} (only called for emergency/urgent or explicit request)`);
|
| 325 |
+
} else {
|
| 326 |
+
logger.info('[REPORT] Hospital tool not executed: no location provided');
|
| 327 |
+
}
|
| 328 |
}
|
| 329 |
+
|
| 330 |
+
logger.info('[REPORT] ✓ All tool executions tracked successfully for report generation');
|
| 331 |
} catch (error) {
|
| 332 |
+
logger.error({ error }, '[REPORT] Failed to track tool executions');
|
| 333 |
// Continue even if tracking fails
|
| 334 |
}
|
| 335 |
|
src/services/tool-execution-tracker.service.ts
CHANGED
|
@@ -64,7 +64,12 @@ export class ToolExecutionTrackerService {
|
|
| 64 |
logger.error({ error }, 'Failed to track tool execution');
|
| 65 |
// Don't throw - tracking failure shouldn't break the workflow
|
| 66 |
} else {
|
| 67 |
-
logger.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
}
|
| 69 |
} catch (error) {
|
| 70 |
logger.error({ error }, 'Error tracking tool execution');
|
|
|
|
| 64 |
logger.error({ error }, 'Failed to track tool execution');
|
| 65 |
// Don't throw - tracking failure shouldn't break the workflow
|
| 66 |
} else {
|
| 67 |
+
logger.info(`[REPORT] ✓ Tool execution saved to database: ${execution.tool_display_name} (${execution.tool_name}, order: ${execution.execution_order}, time: ${execution.execution_time_ms}ms)`);
|
| 68 |
+
logger.debug(`[REPORT] Tool execution data: ${JSON.stringify({
|
| 69 |
+
tool: execution.tool_name,
|
| 70 |
+
input: Object.keys(execution.input_data || {}),
|
| 71 |
+
output_keys: Object.keys(execution.output_data || {})
|
| 72 |
+
})}`);
|
| 73 |
}
|
| 74 |
} catch (error) {
|
| 75 |
logger.error({ error }, 'Error tracking tool execution');
|