agentic-api 2.0.491 → 2.0.585
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/README.md +37 -34
- package/dist/src/agents/reducer.core.js +2 -2
- package/dist/src/agents/simulator.d.ts +26 -1
- package/dist/src/agents/simulator.dashboard.d.ts +140 -0
- package/dist/src/agents/simulator.dashboard.js +344 -0
- package/dist/src/agents/simulator.js +56 -0
- package/dist/src/agents/simulator.types.d.ts +38 -6
- package/dist/src/agents/simulator.utils.d.ts +22 -1
- package/dist/src/agents/simulator.utils.js +27 -0
- package/dist/src/execute/helpers.js +2 -2
- package/dist/src/execute/modelconfig.d.ts +21 -11
- package/dist/src/execute/modelconfig.js +29 -13
- package/dist/src/execute/responses.js +8 -7
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +20 -1
- package/dist/src/llm/config.d.ts +25 -0
- package/dist/src/llm/config.js +38 -0
- package/dist/src/llm/index.d.ts +48 -0
- package/dist/src/llm/index.js +115 -0
- package/dist/src/llm/openai.d.ts +6 -0
- package/dist/src/llm/openai.js +154 -0
- package/dist/src/llm/pricing.d.ts +26 -0
- package/dist/src/llm/pricing.js +129 -0
- package/dist/src/llm/xai.d.ts +17 -0
- package/dist/src/llm/xai.js +90 -0
- package/dist/src/pricing.llm.d.ts +3 -15
- package/dist/src/pricing.llm.js +10 -251
- package/dist/src/prompts.d.ts +0 -1
- package/dist/src/prompts.js +51 -118
- package/dist/src/rag/embeddings.d.ts +5 -1
- package/dist/src/rag/embeddings.js +15 -5
- package/dist/src/rag/parser.js +1 -1
- package/dist/src/rag/rag.manager.d.ts +33 -2
- package/dist/src/rag/rag.manager.js +132 -46
- package/dist/src/rag/types.d.ts +2 -0
- package/dist/src/rag/usecase.js +8 -11
- package/dist/src/rules/git/git.health.js +59 -4
- package/dist/src/rules/git/repo.d.ts +11 -4
- package/dist/src/rules/git/repo.js +64 -18
- package/dist/src/rules/git/repo.pr.d.ts +8 -0
- package/dist/src/rules/git/repo.pr.js +45 -1
- package/dist/src/rules/git/repo.tools.d.ts +5 -1
- package/dist/src/rules/git/repo.tools.js +54 -7
- package/dist/src/rules/types.d.ts +14 -0
- package/dist/src/rules/utils.matter.d.ts +0 -20
- package/dist/src/rules/utils.matter.js +42 -74
- package/dist/src/scrapper.js +2 -2
- package/dist/src/utils.d.ts +0 -8
- package/dist/src/utils.js +1 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,15 +66,24 @@ npm install @agentic-api
|
|
|
66
66
|
|
|
67
67
|
## 💡 Quick Start
|
|
68
68
|
|
|
69
|
+
### Configuration `.env`
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Provider LLM (openai | xai)
|
|
73
|
+
LLM_PROVIDER=openai
|
|
74
|
+
|
|
75
|
+
# Clés API
|
|
76
|
+
OPENAI_API_KEY=sk-... # Requis pour OpenAI + embeddings + whisper
|
|
77
|
+
XAI_API_KEY=xai-... # Requis si LLM_PROVIDER=xai
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Usage
|
|
81
|
+
|
|
69
82
|
```typescript
|
|
70
|
-
import
|
|
71
|
-
import { executeAgentSet } from '@agentic-api';
|
|
72
|
-
import { AgenticContext } from '@agentic-api';
|
|
73
|
-
import { AgentStateGraph } from '@agentic-api';
|
|
83
|
+
import { llmInstance, executeAgentSet, AgenticContext, AgentStateGraph } from '@agentic-api';
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
85
|
+
// Initialiser le LLM (utilise LLM_PROVIDER depuis .env)
|
|
86
|
+
llmInstance();
|
|
78
87
|
|
|
79
88
|
// Create context with user information
|
|
80
89
|
const context: AgenticContext = {
|
|
@@ -357,41 +366,35 @@ const structuredResult = await mapper.reduce(config, structuredCallback, {
|
|
|
357
366
|
|
|
358
367
|
Advanced testing framework for agent behavior validation with scenario-based simulations.
|
|
359
368
|
|
|
360
|
-
- **
|
|
361
|
-
- **
|
|
362
|
-
- **Automatic Validation**: Built-in
|
|
369
|
+
- **Clean API**: Separated `scenario` (context) and `testCase` (test parameters)
|
|
370
|
+
- **Oneshot by Default**: `maxExchanges=1` for simple single-response tests
|
|
371
|
+
- **Automatic Tool Validation**: Built-in validation with `expectedTools`
|
|
363
372
|
- **Exchange Limiting**: Control simulation length with configurable exchange limits
|
|
364
373
|
|
|
365
374
|
📖 **[Complete Agent Simulator Documentation →](./docs/README-AGENT-SIMULATOR.md)**
|
|
366
375
|
|
|
367
376
|
```typescript
|
|
368
|
-
import { AgentSimulator,
|
|
369
|
-
|
|
370
|
-
// Define test scenario
|
|
371
|
-
const scenario: SimulationScenario = {
|
|
372
|
-
testGoals: "Verify that the agent can help with haiku creation",
|
|
373
|
-
testEnd: "Agent provides a complete haiku poem",
|
|
374
|
-
testPersona: "A poetry enthusiast seeking creative assistance",
|
|
375
|
-
testQuery: "I want to write a haiku about nature. Can you help me?",
|
|
376
|
-
testResult: "Agent successfully guides haiku creation process",
|
|
377
|
-
testError: "Agent refuses to help or provides incorrect format"
|
|
378
|
-
};
|
|
377
|
+
import { AgentSimulator, PERSONA_PATIENT } from '@agentic-api';
|
|
379
378
|
|
|
380
379
|
// Configure simulator
|
|
381
380
|
const simulator = new AgentSimulator({
|
|
382
381
|
agents: [haikuAgent, welcomeAgent],
|
|
383
382
|
start: "welcome",
|
|
384
|
-
verbose: true
|
|
385
|
-
instructionEx: "Focus on creative writing assistance"
|
|
383
|
+
verbose: true
|
|
386
384
|
});
|
|
387
385
|
|
|
388
|
-
//
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
386
|
+
// Define test scenario (context)
|
|
387
|
+
const scenario = {
|
|
388
|
+
goals: "Verify that the agent can help with haiku creation. Agent provides a complete haiku poem.",
|
|
389
|
+
persona: PERSONA_PATIENT
|
|
390
|
+
// result defaults to '{"success": boolean, "explain": string, "error": string}'
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Run test case
|
|
394
|
+
const result = await simulator.testCase(scenario, {
|
|
395
|
+
query: "I want to write a haiku about nature. Can you help me?",
|
|
396
|
+
maxExchanges: 5, // defaults to 1 (oneshot)
|
|
397
|
+
expectedTools: { 'transferAgents': { gte: 1 } } // defaults to {}
|
|
395
398
|
});
|
|
396
399
|
|
|
397
400
|
// Validate results
|
|
@@ -406,10 +409,10 @@ if (!result.success) {
|
|
|
406
409
|
|
|
407
410
|
### Simulation Features
|
|
408
411
|
|
|
409
|
-
- **
|
|
410
|
-
- **
|
|
411
|
-
- **
|
|
412
|
-
- **
|
|
412
|
+
- **Separated Concerns**: `scenario` for context, `testCase` for test parameters
|
|
413
|
+
- **Sensible Defaults**: `maxExchanges=1`, `expectedTools={}`, default result format
|
|
414
|
+
- **Persona Simulation**: Built-in personas (PERSONA_PATIENT, PERSONA_PRESSE, PERSONA_ENERVE)
|
|
415
|
+
- **Tool Validation**: Automatic validation with `equal`, `gte`, `lte` constraints
|
|
413
416
|
- **Execution Metadata**: Access to token usage, actions, and performance metrics
|
|
414
417
|
|
|
415
418
|
## 📋 Rules Management System
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MapLLM = void 0;
|
|
7
7
|
const execute_1 = require("../execute");
|
|
8
|
-
const
|
|
8
|
+
const llm_1 = require("../llm");
|
|
9
9
|
/**
|
|
10
10
|
* MapLLM - Orchestrateur principal pour le reduce hiérarchique
|
|
11
11
|
*/
|
|
@@ -54,7 +54,7 @@ class MapLLM {
|
|
|
54
54
|
let totalChunkSize = 0;
|
|
55
55
|
let totalReduce = 0;
|
|
56
56
|
const model = (0, execute_1.modelConfig)(result.model);
|
|
57
|
-
const openai = (0,
|
|
57
|
+
const openai = (0, llm_1.llmInstance)();
|
|
58
58
|
const llm = Object.assign({}, model);
|
|
59
59
|
llm.stream = false;
|
|
60
60
|
delete llm.stream_options;
|
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
import { SimulatorConfig, SimulationOptions, SimulationResult } from './simulator.types';
|
|
1
|
+
import { SimulatorConfig, SimulationOptions, SimulationResult, TestScenario, TestCaseInput } from './simulator.types';
|
|
2
2
|
export declare class AgentSimulator {
|
|
3
3
|
private config;
|
|
4
4
|
private executor;
|
|
5
5
|
private lastExecution?;
|
|
6
6
|
constructor(config: SimulatorConfig);
|
|
7
7
|
/**
|
|
8
|
+
* Exécuter un cas de test avec scénario et paramètres séparés
|
|
9
|
+
*
|
|
10
|
+
* @param scenario - Contexte stable (goals, persona, result)
|
|
11
|
+
* @param testCase - Paramètres du test (query, maxExchanges, model, expectedTools)
|
|
12
|
+
* @returns SimulationResult
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const scenario = {
|
|
17
|
+
* goals: 'Obtenir le nombre secret 1942',
|
|
18
|
+
* persona: PERSONA_PATIENT,
|
|
19
|
+
* result: '{"success": boolean, "error": string}'
|
|
20
|
+
* };
|
|
21
|
+
*
|
|
22
|
+
* const result = await simulator.testCase(scenario, {
|
|
23
|
+
* query: 'À quel nombre penses-tu?',
|
|
24
|
+
* maxExchanges: 3, // défaut: 1 (oneshot)
|
|
25
|
+
* expectedTools: { 'transferAgents': { equal: 1 } } // défaut: {}
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
testCase(scenario: TestScenario, testCase: TestCaseInput): Promise<SimulationResult>;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Utiliser testCase(scenario, case) à la place
|
|
32
|
+
*
|
|
8
33
|
* Exécuter la simulation complète
|
|
9
34
|
*
|
|
10
35
|
* Architecture :
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { TestScenario, TestCaseInput, SimulatorConfig } from './simulator.types';
|
|
2
|
+
import { AgentMessage } from '../stategraph';
|
|
3
|
+
/**
|
|
4
|
+
* Test case combining scenario and case input
|
|
5
|
+
* Format du fichier JSON d'entrée
|
|
6
|
+
*/
|
|
7
|
+
export interface DashboardTestCase {
|
|
8
|
+
id?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
scenario: TestScenario;
|
|
11
|
+
case: TestCaseInput;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Format du fichier JSON d'entrée
|
|
15
|
+
*/
|
|
16
|
+
export interface DashboardInput {
|
|
17
|
+
name?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
config?: Partial<SimulatorConfig>;
|
|
20
|
+
tests: DashboardTestCase[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Status d'exécution d'un test
|
|
24
|
+
*/
|
|
25
|
+
export type TestStatus = 'pending' | 'running' | 'completed' | 'failed' | 'error';
|
|
26
|
+
/**
|
|
27
|
+
* Ligne JSONL pour un résultat de test
|
|
28
|
+
*/
|
|
29
|
+
export interface DashboardOutputLine {
|
|
30
|
+
type: 'start' | 'result' | 'end' | 'error';
|
|
31
|
+
timestamp: string;
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
totalTests?: number;
|
|
34
|
+
testId?: string;
|
|
35
|
+
testIndex?: number;
|
|
36
|
+
name?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
query?: string;
|
|
39
|
+
status?: TestStatus;
|
|
40
|
+
success?: boolean;
|
|
41
|
+
message?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
exchangeCount?: number;
|
|
44
|
+
messages?: AgentMessage[];
|
|
45
|
+
duration?: number;
|
|
46
|
+
summary?: {
|
|
47
|
+
total: number;
|
|
48
|
+
passed: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
errors: number;
|
|
51
|
+
totalDuration: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Status de l'exécution pour le contrôleur backend
|
|
56
|
+
*/
|
|
57
|
+
export interface DashboardStatus {
|
|
58
|
+
isRunning: boolean;
|
|
59
|
+
sessionId: string | null;
|
|
60
|
+
currentTest: number;
|
|
61
|
+
totalTests: number;
|
|
62
|
+
passed: number;
|
|
63
|
+
failed: number;
|
|
64
|
+
errors: number;
|
|
65
|
+
startTime: Date | null;
|
|
66
|
+
lastUpdate: Date | null;
|
|
67
|
+
}
|
|
68
|
+
export declare class SimulatorDashboard {
|
|
69
|
+
private config;
|
|
70
|
+
private status;
|
|
71
|
+
private simulator;
|
|
72
|
+
private abortController;
|
|
73
|
+
private _currentOutputPath;
|
|
74
|
+
private _currentInputPath;
|
|
75
|
+
constructor(config: SimulatorConfig);
|
|
76
|
+
/**
|
|
77
|
+
* Chemin du fichier output actuel (ou du dernier run)
|
|
78
|
+
*/
|
|
79
|
+
get currentOutputPath(): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Chemin du fichier input actuel (ou du dernier run)
|
|
82
|
+
*/
|
|
83
|
+
get currentInputPath(): string | null;
|
|
84
|
+
private createInitialStatus;
|
|
85
|
+
/**
|
|
86
|
+
* Obtenir le status actuel (pour le contrôleur backend)
|
|
87
|
+
*/
|
|
88
|
+
getStatus(): DashboardStatus;
|
|
89
|
+
/**
|
|
90
|
+
* Vérifier si une exécution est en cours
|
|
91
|
+
*/
|
|
92
|
+
isRunning(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Annuler l'exécution en cours
|
|
95
|
+
*/
|
|
96
|
+
abort(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Charger un fichier JSON d'entrée
|
|
99
|
+
*/
|
|
100
|
+
loadInputFile(filePath: string): Promise<DashboardInput>;
|
|
101
|
+
/**
|
|
102
|
+
* Générer le chemin du fichier output basé sur le fichier input
|
|
103
|
+
* Exemple: tests/my-tests.json → tests/results.my-tests.jsonl
|
|
104
|
+
*/
|
|
105
|
+
createOutputPath(inputPath: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Charger les résultats JSONL existants (en cours ou terminés)
|
|
108
|
+
* Supporte le streaming partiel (fichier en cours d'écriture)
|
|
109
|
+
*
|
|
110
|
+
* @param inputPath - Chemin du fichier JSON d'entrée (génère automatiquement le output path)
|
|
111
|
+
* @returns Les lignes parsées ou null si le fichier n'existe pas
|
|
112
|
+
*/
|
|
113
|
+
loadResults(inputPath?: string): Promise<DashboardOutputLine[] | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Obtenir le résumé des résultats (dernière ligne type='end')
|
|
116
|
+
* @param inputPath - Chemin du fichier JSON d'entrée
|
|
117
|
+
*/
|
|
118
|
+
getResultsSummary(inputPath?: string): Promise<DashboardOutputLine['summary'] | null>;
|
|
119
|
+
/**
|
|
120
|
+
* Vérifier si les résultats sont complets (contient une ligne 'end')
|
|
121
|
+
* @param inputPath - Chemin du fichier JSON d'entrée
|
|
122
|
+
*/
|
|
123
|
+
isResultsComplete(inputPath?: string): Promise<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* Exécuter les tests et écrire les résultats en JSONL
|
|
126
|
+
*
|
|
127
|
+
* @param input - Données d'entrée (ou chemin vers fichier JSON)
|
|
128
|
+
* @param outputPath - Chemin du fichier JSONL de sortie
|
|
129
|
+
* @param onLine - Callback optionnel pour chaque ligne JSONL (streaming)
|
|
130
|
+
*/
|
|
131
|
+
run(input: DashboardInput | string, outputPath?: string, onLine?: (line: DashboardOutputLine) => void): Promise<DashboardOutputLine[]>;
|
|
132
|
+
/**
|
|
133
|
+
* Exécuter avec callback de streaming (pour SSE/WebSocket)
|
|
134
|
+
*/
|
|
135
|
+
runWithStream(input: DashboardInput | string, onLine: (line: DashboardOutputLine) => void): Promise<DashboardOutputLine[]>;
|
|
136
|
+
/**
|
|
137
|
+
* Créer un fichier JSON d'exemple pour les tests
|
|
138
|
+
*/
|
|
139
|
+
static createExampleInput(): DashboardInput;
|
|
140
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SimulatorDashboard = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const promises_1 = require("fs/promises");
|
|
9
|
+
const readline_1 = require("readline");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const simulator_1 = require("./simulator");
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// CLASS: SimulatorDashboard
|
|
14
|
+
// ============================================================================
|
|
15
|
+
class SimulatorDashboard {
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.simulator = null;
|
|
19
|
+
this.abortController = null;
|
|
20
|
+
this._currentOutputPath = null;
|
|
21
|
+
this._currentInputPath = null;
|
|
22
|
+
this.status = this.createInitialStatus();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Chemin du fichier output actuel (ou du dernier run)
|
|
26
|
+
*/
|
|
27
|
+
get currentOutputPath() {
|
|
28
|
+
return this._currentOutputPath;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Chemin du fichier input actuel (ou du dernier run)
|
|
32
|
+
*/
|
|
33
|
+
get currentInputPath() {
|
|
34
|
+
return this._currentInputPath;
|
|
35
|
+
}
|
|
36
|
+
createInitialStatus() {
|
|
37
|
+
return {
|
|
38
|
+
isRunning: false,
|
|
39
|
+
sessionId: null,
|
|
40
|
+
currentTest: 0,
|
|
41
|
+
totalTests: 0,
|
|
42
|
+
passed: 0,
|
|
43
|
+
failed: 0,
|
|
44
|
+
errors: 0,
|
|
45
|
+
startTime: null,
|
|
46
|
+
lastUpdate: null
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Obtenir le status actuel (pour le contrôleur backend)
|
|
51
|
+
*/
|
|
52
|
+
getStatus() {
|
|
53
|
+
return { ...this.status };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Vérifier si une exécution est en cours
|
|
57
|
+
*/
|
|
58
|
+
isRunning() {
|
|
59
|
+
return this.status.isRunning;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Annuler l'exécution en cours
|
|
63
|
+
*/
|
|
64
|
+
abort() {
|
|
65
|
+
if (this.abortController) {
|
|
66
|
+
this.abortController.abort();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Charger un fichier JSON d'entrée
|
|
71
|
+
*/
|
|
72
|
+
async loadInputFile(filePath) {
|
|
73
|
+
const content = await (0, promises_1.readFile)(filePath, 'utf-8');
|
|
74
|
+
return JSON.parse(content);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Générer le chemin du fichier output basé sur le fichier input
|
|
78
|
+
* Exemple: tests/my-tests.json → tests/results.my-tests.jsonl
|
|
79
|
+
*/
|
|
80
|
+
createOutputPath(inputPath) {
|
|
81
|
+
const dir = path_1.default.dirname(inputPath);
|
|
82
|
+
const basename = path_1.default.basename(inputPath, path_1.default.extname(inputPath));
|
|
83
|
+
return path_1.default.join(dir, `results.${basename}.jsonl`);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Charger les résultats JSONL existants (en cours ou terminés)
|
|
87
|
+
* Supporte le streaming partiel (fichier en cours d'écriture)
|
|
88
|
+
*
|
|
89
|
+
* @param inputPath - Chemin du fichier JSON d'entrée (génère automatiquement le output path)
|
|
90
|
+
* @returns Les lignes parsées ou null si le fichier n'existe pas
|
|
91
|
+
*/
|
|
92
|
+
async loadResults(inputPath) {
|
|
93
|
+
// Déterminer le chemin du fichier output
|
|
94
|
+
let outputPath = null;
|
|
95
|
+
if (inputPath) {
|
|
96
|
+
outputPath = this.createOutputPath(inputPath);
|
|
97
|
+
}
|
|
98
|
+
else if (this._currentInputPath) {
|
|
99
|
+
outputPath = this.createOutputPath(this._currentInputPath);
|
|
100
|
+
}
|
|
101
|
+
else if (this._currentOutputPath) {
|
|
102
|
+
outputPath = this._currentOutputPath;
|
|
103
|
+
}
|
|
104
|
+
if (!outputPath || !(0, fs_1.existsSync)(outputPath)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const lines = [];
|
|
109
|
+
const stream = (0, fs_1.createReadStream)(outputPath, { encoding: 'utf-8' });
|
|
110
|
+
const rl = (0, readline_1.createInterface)({ input: stream, crlfDelay: Infinity });
|
|
111
|
+
rl.on('line', (line) => {
|
|
112
|
+
if (line.trim()) {
|
|
113
|
+
try {
|
|
114
|
+
lines.push(JSON.parse(line));
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
// Ignorer les lignes mal formées (fichier en cours d'écriture)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
rl.on('close', () => resolve(lines));
|
|
122
|
+
rl.on('error', reject);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Obtenir le résumé des résultats (dernière ligne type='end')
|
|
127
|
+
* @param inputPath - Chemin du fichier JSON d'entrée
|
|
128
|
+
*/
|
|
129
|
+
async getResultsSummary(inputPath) {
|
|
130
|
+
const results = await this.loadResults(inputPath);
|
|
131
|
+
if (!results)
|
|
132
|
+
return null;
|
|
133
|
+
const endLine = results.find(r => r.type === 'end');
|
|
134
|
+
return endLine?.summary || null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Vérifier si les résultats sont complets (contient une ligne 'end')
|
|
138
|
+
* @param inputPath - Chemin du fichier JSON d'entrée
|
|
139
|
+
*/
|
|
140
|
+
async isResultsComplete(inputPath) {
|
|
141
|
+
const results = await this.loadResults(inputPath);
|
|
142
|
+
if (!results)
|
|
143
|
+
return false;
|
|
144
|
+
return results.some(r => r.type === 'end');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Exécuter les tests et écrire les résultats en JSONL
|
|
148
|
+
*
|
|
149
|
+
* @param input - Données d'entrée (ou chemin vers fichier JSON)
|
|
150
|
+
* @param outputPath - Chemin du fichier JSONL de sortie
|
|
151
|
+
* @param onLine - Callback optionnel pour chaque ligne JSONL (streaming)
|
|
152
|
+
*/
|
|
153
|
+
async run(input, outputPath, onLine) {
|
|
154
|
+
// Stocker et charger l'input si c'est un chemin
|
|
155
|
+
const isInputFile = typeof input === 'string';
|
|
156
|
+
this._currentInputPath = isInputFile ? input : null;
|
|
157
|
+
const data = isInputFile
|
|
158
|
+
? await this.loadInputFile(input)
|
|
159
|
+
: input;
|
|
160
|
+
// Générer automatiquement le outputPath si input est un fichier
|
|
161
|
+
const resolvedOutputPath = outputPath ?? (isInputFile ? this.createOutputPath(input) : undefined);
|
|
162
|
+
this._currentOutputPath = resolvedOutputPath || null;
|
|
163
|
+
// Initialiser le status
|
|
164
|
+
const sessionId = `session-${Date.now()}`;
|
|
165
|
+
this.status = {
|
|
166
|
+
isRunning: true,
|
|
167
|
+
sessionId,
|
|
168
|
+
currentTest: 0,
|
|
169
|
+
totalTests: data.tests.length,
|
|
170
|
+
passed: 0,
|
|
171
|
+
failed: 0,
|
|
172
|
+
errors: 0,
|
|
173
|
+
startTime: new Date(),
|
|
174
|
+
lastUpdate: new Date()
|
|
175
|
+
};
|
|
176
|
+
// Créer le simulateur avec config mergée
|
|
177
|
+
const mergedConfig = {
|
|
178
|
+
...this.config,
|
|
179
|
+
...data.config
|
|
180
|
+
};
|
|
181
|
+
this.simulator = new simulator_1.AgentSimulator(mergedConfig);
|
|
182
|
+
// Créer l'AbortController
|
|
183
|
+
this.abortController = new AbortController();
|
|
184
|
+
// Ouvrir le fichier de sortie si spécifié
|
|
185
|
+
let writeStream = null;
|
|
186
|
+
if (resolvedOutputPath) {
|
|
187
|
+
writeStream = (0, fs_1.createWriteStream)(resolvedOutputPath, { encoding: 'utf-8' });
|
|
188
|
+
}
|
|
189
|
+
const results = [];
|
|
190
|
+
const startTime = Date.now();
|
|
191
|
+
// Helper pour écrire une ligne
|
|
192
|
+
const writeLine = (line) => {
|
|
193
|
+
results.push(line);
|
|
194
|
+
const jsonLine = JSON.stringify(line) + '\n';
|
|
195
|
+
if (writeStream) {
|
|
196
|
+
writeStream.write(jsonLine);
|
|
197
|
+
}
|
|
198
|
+
if (onLine) {
|
|
199
|
+
onLine(line);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
// Écrire la ligne de début
|
|
204
|
+
writeLine({
|
|
205
|
+
type: 'start',
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
sessionId,
|
|
208
|
+
totalTests: data.tests.length
|
|
209
|
+
});
|
|
210
|
+
// Exécuter chaque test
|
|
211
|
+
for (let i = 0; i < data.tests.length; i++) {
|
|
212
|
+
// Vérifier si annulé
|
|
213
|
+
if (this.abortController.signal.aborted) {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
const testCase = data.tests[i];
|
|
217
|
+
const testId = testCase.id || `test-${i}`;
|
|
218
|
+
const testStartTime = Date.now();
|
|
219
|
+
// Mettre à jour le status
|
|
220
|
+
this.status.currentTest = i + 1;
|
|
221
|
+
this.status.lastUpdate = new Date();
|
|
222
|
+
try {
|
|
223
|
+
// Exécuter le test
|
|
224
|
+
const result = await this.simulator.testCase(testCase.scenario, testCase.case);
|
|
225
|
+
// Déterminer le status
|
|
226
|
+
const status = result.success ? 'completed' : 'failed';
|
|
227
|
+
// Mettre à jour les compteurs
|
|
228
|
+
if (result.success) {
|
|
229
|
+
this.status.passed++;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.status.failed++;
|
|
233
|
+
}
|
|
234
|
+
// Écrire le résultat
|
|
235
|
+
writeLine({
|
|
236
|
+
type: 'result',
|
|
237
|
+
timestamp: new Date().toISOString(),
|
|
238
|
+
testId,
|
|
239
|
+
testIndex: i,
|
|
240
|
+
name: testCase.name || testId,
|
|
241
|
+
description: testCase.scenario.description || testCase.scenario.goals,
|
|
242
|
+
query: testCase.case.query,
|
|
243
|
+
status,
|
|
244
|
+
success: result.success,
|
|
245
|
+
message: result.message,
|
|
246
|
+
error: result.error || undefined,
|
|
247
|
+
exchangeCount: result.exchangeCount,
|
|
248
|
+
messages: result.messages,
|
|
249
|
+
duration: Date.now() - testStartTime
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
// Erreur d'exécution
|
|
254
|
+
this.status.errors++;
|
|
255
|
+
writeLine({
|
|
256
|
+
type: 'error',
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
testId,
|
|
259
|
+
testIndex: i,
|
|
260
|
+
name: testCase.name || testId,
|
|
261
|
+
description: testCase.scenario.description || testCase.scenario.goals,
|
|
262
|
+
query: testCase.case.query,
|
|
263
|
+
status: 'error',
|
|
264
|
+
success: false,
|
|
265
|
+
error: error.message || String(error),
|
|
266
|
+
duration: Date.now() - testStartTime
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Écrire la ligne de fin
|
|
271
|
+
writeLine({
|
|
272
|
+
type: 'end',
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
sessionId,
|
|
275
|
+
summary: {
|
|
276
|
+
total: data.tests.length,
|
|
277
|
+
passed: this.status.passed,
|
|
278
|
+
failed: this.status.failed,
|
|
279
|
+
errors: this.status.errors,
|
|
280
|
+
totalDuration: Date.now() - startTime
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
// Fermer le stream et attendre la fin de l'écriture
|
|
286
|
+
if (writeStream) {
|
|
287
|
+
await new Promise((resolve, reject) => {
|
|
288
|
+
writeStream.end(() => resolve());
|
|
289
|
+
writeStream.on('error', reject);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Reset le status
|
|
293
|
+
this.status.isRunning = false;
|
|
294
|
+
this.status.lastUpdate = new Date();
|
|
295
|
+
this.simulator = null;
|
|
296
|
+
this.abortController = null;
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Exécuter avec callback de streaming (pour SSE/WebSocket)
|
|
302
|
+
*/
|
|
303
|
+
async runWithStream(input, onLine) {
|
|
304
|
+
return this.run(input, undefined, onLine);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Créer un fichier JSON d'exemple pour les tests
|
|
308
|
+
*/
|
|
309
|
+
static createExampleInput() {
|
|
310
|
+
return {
|
|
311
|
+
name: "Example Test Suite",
|
|
312
|
+
description: "Exemple de suite de tests pour le simulator",
|
|
313
|
+
tests: [
|
|
314
|
+
{
|
|
315
|
+
id: "test-greeting",
|
|
316
|
+
name: "Test de salutation",
|
|
317
|
+
scenario: {
|
|
318
|
+
goals: "L'agent doit répondre poliment à la salutation",
|
|
319
|
+
persona: "Utilisateur poli et patient"
|
|
320
|
+
},
|
|
321
|
+
case: {
|
|
322
|
+
query: "Bonjour, comment allez-vous?"
|
|
323
|
+
// maxExchanges: 1 par défaut
|
|
324
|
+
// expectedTools: {} par défaut
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "test-transfer",
|
|
329
|
+
name: "Test de transfert",
|
|
330
|
+
scenario: {
|
|
331
|
+
goals: "L'agent doit transférer vers l'agent spécialisé",
|
|
332
|
+
persona: "Utilisateur avec une demande spécifique"
|
|
333
|
+
},
|
|
334
|
+
case: {
|
|
335
|
+
query: "J'ai besoin d'aide spécialisée",
|
|
336
|
+
maxExchanges: 3,
|
|
337
|
+
expectedTools: { 'transferAgents': { gte: 1 } }
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
exports.SimulatorDashboard = SimulatorDashboard;
|