@lvx74/openrrouter-ai-agent 1.0.8 β 1.0.9
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/package.json +1 -1
- package/src/Agent.js +6 -1
- package/src/index.js +3 -0
- package/src/lib/ai-client.js +27 -0
- package/src/lib/utils.js +83 -0
- package/src/prompts/summarize_conversation_prompt.txt +37 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lvx74/openrrouter-ai-agent",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.9",
|
4
4
|
"description": "A powerful AI agent toolkit compatible with @openai/agents for building conversational AI with tool calling support using OpenRouter",
|
5
5
|
"type": "module",
|
6
6
|
"main": "src/index.js",
|
package/src/Agent.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import 'dotenv/config'
|
2
2
|
import OpenAI from 'openai'
|
3
3
|
import { EventEmitter } from 'events'
|
4
|
+
import { checkAndCompressHistory } from './lib/utils.js'
|
4
5
|
|
5
6
|
/**
|
6
7
|
* Classe Agent compatibile con @openai/agents per gestire conversazioni con AI e tool calls
|
@@ -95,6 +96,8 @@ export class Agent extends EventEmitter {
|
|
95
96
|
console.log('Ultimo messaggio:', this.getLastMessage())
|
96
97
|
console.log('Tools:', toolDefinitions.map(t => t.name).join(', '))
|
97
98
|
}
|
99
|
+
this.messages = await checkAndCompressHistory(this.messages)
|
100
|
+
|
98
101
|
const res = await this.openai.chat.completions.create({
|
99
102
|
model: this.model,
|
100
103
|
messages: this.messages,
|
@@ -102,7 +105,9 @@ export class Agent extends EventEmitter {
|
|
102
105
|
tool_choice: toolDefinitions.length > 0 ? 'auto' : undefined,
|
103
106
|
temperature: this.temperature
|
104
107
|
})
|
105
|
-
|
108
|
+
if (res.error) {
|
109
|
+
throw new Error(`OpenAI API Error: ${res.error || 'Unknown error'}`)
|
110
|
+
}
|
106
111
|
const msg = res.choices[0].message
|
107
112
|
|
108
113
|
if (this.debug) {
|
package/src/index.js
CHANGED
@@ -39,6 +39,9 @@ export { Tool } from './Tool.js'
|
|
39
39
|
// Utility functions
|
40
40
|
export { createChatInterface } from './cli.js'
|
41
41
|
|
42
|
+
export { parseJSON, checkAndCompressHistory } from './lib/utils.js'
|
43
|
+
export { callAI } from './lib/ai-client.js'
|
44
|
+
|
42
45
|
/**
|
43
46
|
* Quick setup function for common use cases
|
44
47
|
*/
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import OpenAI from 'openai'
|
2
|
+
|
3
|
+
export async function callAI(prompt, temperature = 0.7, model = process.env.MODEL ||'qwen/qwen3-4b:free', systemPrompt = 'You are a helpful assistant.', retry = 10, stream = false) {
|
4
|
+
const openai = new OpenAI({
|
5
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
6
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
7
|
+
})
|
8
|
+
try {
|
9
|
+
const res = await openai.chat.completions.create({
|
10
|
+
model,
|
11
|
+
prompt: `${systemPrompt}\n\nUser: ${prompt}`,
|
12
|
+
temperature: temperature
|
13
|
+
})
|
14
|
+
if(res.error) {
|
15
|
+
throw new Error(`OpenAI API error: ${res.error.message}`);
|
16
|
+
}
|
17
|
+
const msg = res.choices[0].text.trim();
|
18
|
+
return msg
|
19
|
+
} catch (error) {
|
20
|
+
// Mostra il body della risposta se disponibile (es. 400)
|
21
|
+
if (error.response && error.response.data) {
|
22
|
+
console.error('API error response:', error.response.data);
|
23
|
+
}
|
24
|
+
console.error(error);
|
25
|
+
throw error;
|
26
|
+
}
|
27
|
+
}
|
package/src/lib/utils.js
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
import { callAI } from './ai-client.js';
|
2
|
+
import fs from 'fs';
|
3
|
+
import { fileURLToPath } from 'url';
|
4
|
+
import { dirname } from 'path';
|
5
|
+
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const __dirname = dirname(__filename);
|
8
|
+
|
9
|
+
export async function parseJSON(content, tryAgain = true, logger = console) {
|
10
|
+
|
11
|
+
logger.log('π parseJSON - Contenuto ricevuto (primi 200 caratteri):', content.substring(0, 200));
|
12
|
+
|
13
|
+
try {
|
14
|
+
// Tenta di trovare il primo blocco JSON valido
|
15
|
+
if (content.startsWith('\\boxed{')) {
|
16
|
+
content = content.replace('\\boxed{', '')
|
17
|
+
//rimouvi l'ultimo carttere
|
18
|
+
content = content.slice(0, -1);
|
19
|
+
}
|
20
|
+
|
21
|
+
const start = content.indexOf('{');
|
22
|
+
const end = content.lastIndexOf('}');
|
23
|
+
|
24
|
+
logger.log('π parseJSON - Posizioni JSON:', { start, end, contentLength: content.length });
|
25
|
+
|
26
|
+
if (start === -1 || end === -1) {
|
27
|
+
console.error('β Nessun blocco JSON trovato nel contenuto');
|
28
|
+
logger.log('π Contenuto completo:', content);
|
29
|
+
throw new Error('Blocco JSON non trovato');
|
30
|
+
}
|
31
|
+
|
32
|
+
let jsonString = content.substring(start, end + 1);
|
33
|
+
logger.log('π parseJSON - JSON estratto (primi 200 caratteri):', jsonString.substring(0, 200));
|
34
|
+
|
35
|
+
// Escape virgolette interne per evitare crash
|
36
|
+
jsonString = jsonString.replace(/:\s*"([^"]*?)"(?=\s*,|\s*})/g, (match, group) => {
|
37
|
+
const escaped = group.replace(/"/g, '\\"');
|
38
|
+
return `: "${escaped}"`;
|
39
|
+
});
|
40
|
+
|
41
|
+
try {
|
42
|
+
const parsed = JSON.parse(jsonString);
|
43
|
+
logger.log('β
parseJSON - JSON parsato con successo');
|
44
|
+
return parsed;
|
45
|
+
} catch (error) {
|
46
|
+
if (!tryAgain) {
|
47
|
+
console.error('β οΈ Errore nel parsing JSON (nessun retry):', error.message);
|
48
|
+
logger.log('π JSON fallito:', jsonString);
|
49
|
+
throw error; // Rilancia l'errore se non si vuole riprovare
|
50
|
+
}
|
51
|
+
logger.log('β οΈ Errore nel parsing JSON, tentativo di correzione:', error.message);
|
52
|
+
logger.log('π JSON malformato:', jsonString);
|
53
|
+
|
54
|
+
//cerco di correggerlo con ai
|
55
|
+
const prompt = `Correggi il seguente JSON malformato. Assicurati che sia un JSON valido e restituisci SOLO il JSON corretto, senza alcuna introduzione o spiegazione.\n\nJSON da correggere:\n${jsonString}`;
|
56
|
+
const corrected = await callAI(prompt, 0.2, process.env.MODEL_CORREZIONE_JSON || process.env.SMALL_MODEL || 'qwen/qwen3-4b:free', 2, false);
|
57
|
+
return parseJSON(corrected, false); // Riprova senza ulteriori tentativi
|
58
|
+
}
|
59
|
+
} catch (e) {
|
60
|
+
console.error('β Errore fatale nel parsing JSON:', e.message);
|
61
|
+
logger.log('π Contenuto originale completo:', content);
|
62
|
+
throw e;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
export async function checkAndCompressHistory(history) {
|
67
|
+
if (!Array.isArray(history)) {
|
68
|
+
throw new Error('La cronologia deve essere un array');
|
69
|
+
}
|
70
|
+
|
71
|
+
if (history.length > (process.env.MAX_HISTORY_LENGTH || 50)) {
|
72
|
+
const latest = history.slice(-4);
|
73
|
+
const data = history.slice(0, -4).filter(m=>m.role !== 'system');
|
74
|
+
const prompt = fs.readFileSync(join(__dirname, '../prompts/compress_history_prompt.txt'), 'utf-8') + '\n' + JSON.stringify(data, null, 2);
|
75
|
+
const compressed = await callAI(prompt, 0.2, process.env.SMALL_MODEL );
|
76
|
+
const parsed = await parseJSON(compressed);
|
77
|
+
if (!parsed || !Array.isArray(parsed)) {
|
78
|
+
throw new Error('La risposta compressa non Γ¨ un array valido');
|
79
|
+
}
|
80
|
+
return [...parsed, ...latest];
|
81
|
+
}
|
82
|
+
return history;
|
83
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Sei un assistente AI incaricato di riassumere la conversazione tra utente e assistente.
|
2
|
+
Riceverai una lista di messaggi in formato JSON (con campi role e content).
|
3
|
+
Il tuo compito Γ¨ comprimere la history mantenendo solo le informazioni essenziali, eliminando ridondanze e dettagli superflui, ma preservando il senso e il contesto della conversazione.
|
4
|
+
Restituisci sempre e solo un JSON valido, con la stessa struttura (array di messaggi, ciascuno con role e content), pronto per essere usato come nuova history.
|
5
|
+
Non aggiungere testo fuori dal JSON, nΓ© spiegazioni.
|
6
|
+
Se necessario, accorpa piΓΉ messaggi simili in uno solo, mantenendo la coerenza.
|
7
|
+
Esempio output:
|
8
|
+
[
|
9
|
+
{"role": "user", "content": "L'utente chiede informazioni su X."},
|
10
|
+
{"role": "assistant", "content": "Risposta sintetica su X."},
|
11
|
+
{
|
12
|
+
"role": "assistant",
|
13
|
+
"content": null,
|
14
|
+
"tool_calls": [
|
15
|
+
{
|
16
|
+
"id": "call_ieIgcfAISt6wlqizZPMiuQ",
|
17
|
+
"index": 11,
|
18
|
+
"type": "function",
|
19
|
+
"function": {
|
20
|
+
"name": "open_chapter",
|
21
|
+
"arguments": "{\"number\":1}"
|
22
|
+
},
|
23
|
+
"done": true,
|
24
|
+
"result": {
|
25
|
+
"success": true,
|
26
|
+
"message": "Adesso aggiungi contenuto sviluppando: bit mancante utilizzando append_to_chapter",
|
27
|
+
"next": {
|
28
|
+
"action": "append_to_chapter",
|
29
|
+
"number": 1,
|
30
|
+
"bit": 0
|
31
|
+
}
|
32
|
+
},
|
33
|
+
"execution_time": 1
|
34
|
+
}
|
35
|
+
]
|
36
|
+
}
|
37
|
+
]
|