Compare commits
3 Commits
codex/fix-
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| c4c6063c6e | |||
| 429114f272 | |||
| 74579375b3 |
@@ -5,7 +5,8 @@ Last updated: 2026-07-11
|
|||||||
## Current DAVE Telegram Style Fix
|
## Current DAVE Telegram Style Fix
|
||||||
|
|
||||||
- Operator feedback from 2026-07-11 screenshots: DAVE still sent large Telegram report blocks, legacy `CRUCIX ROUTINE` fallback alerts, visible report labels such as `Confidence`, and tool/audit traces such as `Werkzeuge: get_security_profile`.
|
- Operator feedback from 2026-07-11 screenshots: DAVE still sent large Telegram report blocks, legacy `CRUCIX ROUTINE` fallback alerts, visible report labels such as `Confidence`, and tool/audit traces such as `Werkzeuge: get_security_profile`.
|
||||||
- Active branch: `codex/fix-dave-telegram-human-style`.
|
- Follow-up feedback from 2026-07-11 18:18: DAVE sometimes stopped answering with `The agent returned no usable response` / `AI chat failed`, and visible labels like `[DAVE // HINWEIS]` still felt like system blocks.
|
||||||
|
- Current follow-up branch: `codex/fix-dave-agent-timeout-fallback`.
|
||||||
- Intent: DAVE should write like a human Telegram contact: short messages, one thought per message, no visible Markdown/report sections, and no automatic tool traces in normal chat.
|
- Intent: DAVE should write like a human Telegram contact: short messages, one thought per message, no visible Markdown/report sections, and no automatic tool traces in normal chat.
|
||||||
- Code changes in progress:
|
- Code changes in progress:
|
||||||
- `TelegramAlerter.sendConversation(...)` now defaults to 420-character chat chunks and can split long paragraphs by sentence.
|
- `TelegramAlerter.sendConversation(...)` now defaults to 420-character chat chunks and can split long paragraphs by sentence.
|
||||||
@@ -13,7 +14,10 @@ Last updated: 2026-07-11
|
|||||||
- Normal Telegram AI answers no longer append tool trace suffixes; `/trace` remains the explicit audit command.
|
- Normal Telegram AI answers no longer append tool trace suffixes; `/trace` remains the explicit audit command.
|
||||||
- Proactive DAVE and dynamic presence prompts request short human-style Telegram messages; evidence is capped and tool names are hidden from visible chat.
|
- Proactive DAVE and dynamic presence prompts request short human-style Telegram messages; evidence is capped and tool names are hidden from visible chat.
|
||||||
- DAVE persona and terminal-agent protocol now require short natural chat answers for proactive/presence responses.
|
- DAVE persona and terminal-agent protocol now require short natural chat answers for proactive/presence responses.
|
||||||
- Tests added in `test/telegram-chat.test.mjs` to verify short default chunks and conversational fallback alerts without `CRUCIX ROUTINE`, `Confidence:`, `Direction:`, or visible tool traces.
|
- Follow-up removes visible `[DAVE // ...]` headers from proactive/presence/fallback Telegram messages.
|
||||||
|
- Follow-up catches empty model responses and LLM timeout/fetch failures inside the terminal agent, returning a short local-sweep fallback instead of `no usable response` or `AI chat failed`.
|
||||||
|
- The fallback tells the operator which terminal surfaces DAVE can control from chat: status, sources, briefings, markets, evidence/web search, memory, scenarios, and confirmed sweeps/alerts.
|
||||||
|
- Tests added in `test/telegram-chat.test.mjs` and `test/terminal-agent.test.mjs` to verify short default chunks, conversational fallback alerts without `CRUCIX ROUTINE`, no visible DAVE system labels, no tool traces, empty-response repair, and timeout fallback to the local sweep.
|
||||||
|
|
||||||
## Source Degradation Handling
|
## Source Degradation Handling
|
||||||
|
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export class DavePresence {
|
|||||||
const evidence = result.evidence?.length
|
const evidence = result.evidence?.length
|
||||||
? `${german ? 'Belege' : 'Evidence'}:\n${result.evidence.slice(0, 2).map(item => `- ${String(item).slice(0, 120)}`).join('\n')}`
|
? `${german ? 'Belege' : 'Evidence'}:\n${result.evidence.slice(0, 2).map(item => `- ${String(item).slice(0, 120)}`).join('\n')}`
|
||||||
: '';
|
: '';
|
||||||
const messages = [`[DAVE // ${german ? 'AKTIV' : 'ACTIVE'}]\n${result.answer}`, evidence].filter(Boolean);
|
const messages = [result.answer, evidence].filter(Boolean);
|
||||||
const sent = typeof this.alerter.sendConversation === 'function'
|
const sent = typeof this.alerter.sendConversation === 'function'
|
||||||
? await this.alerter.sendConversation(messages, { maxChunkChars: 380 })
|
? await this.alerter.sendConversation(messages, { maxChunkChars: 380 })
|
||||||
: await this.alerter.sendMessage(messages.join('\n\n'), { parseMode: null });
|
: await this.alerter.sendMessage(messages.join('\n\n'), { parseMode: null });
|
||||||
|
|||||||
@@ -86,13 +86,25 @@ export class TerminalAgent {
|
|||||||
].join('\n\n');
|
].join('\n\n');
|
||||||
|
|
||||||
for (let step = 0; step < this.maxSteps; step++) {
|
for (let step = 0; step < this.maxSteps; step++) {
|
||||||
const response = await this.provider.complete(this._systemPrompt(mode, responseLanguage), working, {
|
let response;
|
||||||
|
try {
|
||||||
|
response = await this.provider.complete(this._systemPrompt(mode, responseLanguage), working, {
|
||||||
maxTokens: this.maxTokens,
|
maxTokens: this.maxTokens,
|
||||||
timeout: this.timeoutMs,
|
timeout: this.timeoutMs,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
trace.push({ tool: 'llm', status: 'failed', durationMs: 0, rationale: short(error.message || error) });
|
||||||
|
const result = localFallbackResult(input, context, responseLanguage, trace, error);
|
||||||
|
this.lastTraceByChat.set(key, trace);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
const decision = parseDecision(response?.text);
|
const decision = parseDecision(response?.text);
|
||||||
if (!decision) {
|
if (!decision) {
|
||||||
const answer = String(response?.text || '').trim() || 'The agent returned no usable response.';
|
const answer = String(response?.text || '').trim();
|
||||||
|
if (!answer) {
|
||||||
|
working += '\n\nPROTOCOL ERROR: The model returned an empty response. Return the documented JSON final object, or call one allowlisted tool if needed.';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (looksLikeProtocolPayload(answer)) {
|
if (looksLikeProtocolPayload(answer)) {
|
||||||
working += '\n\nPROTOCOL ERROR: The previous tool syntax was malformed. Return the documented JSON tool_call or final object only.';
|
working += '\n\nPROTOCOL ERROR: The previous tool syntax was malformed. Return the documented JSON tool_call or final object only.';
|
||||||
continue;
|
continue;
|
||||||
@@ -146,10 +158,18 @@ export class TerminalAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let attempt = 0; attempt < 2; attempt++) {
|
for (let attempt = 0; attempt < 2; attempt++) {
|
||||||
const response = await this.provider.complete(this._finalPrompt(mode, responseLanguage), `${working}\n\nFINALIZATION ATTEMPT ${attempt + 1}: Synthesize the answer from the evidence above.`, {
|
let response;
|
||||||
|
try {
|
||||||
|
response = await this.provider.complete(this._finalPrompt(mode, responseLanguage), `${working}\n\nFINALIZATION ATTEMPT ${attempt + 1}: Synthesize the answer from the evidence above.`, {
|
||||||
maxTokens: this.maxTokens,
|
maxTokens: this.maxTokens,
|
||||||
timeout: this.timeoutMs,
|
timeout: this.timeoutMs,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
trace.push({ tool: 'llm_final', status: 'failed', durationMs: 0, rationale: short(error.message || error) });
|
||||||
|
const result = localFallbackResult(input, context, responseLanguage, trace, error);
|
||||||
|
this.lastTraceByChat.set(key, trace);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
const decision = parseDecision(response?.text);
|
const decision = parseDecision(response?.text);
|
||||||
if (decision?.type === 'final') {
|
if (decision?.type === 'final') {
|
||||||
const result = finalResult(decision, trace);
|
const result = finalResult(decision, trace);
|
||||||
@@ -351,6 +371,74 @@ function finalizationFailureMessage(input) {
|
|||||||
: 'I could not reliably synthesize the retrieved evidence into an answer. Internal tool data was not exposed. Please retry the question or cautiously increase TELEGRAM_AGENT_MAX_STEPS.';
|
: 'I could not reliably synthesize the retrieved evidence into an answer. Internal tool data was not exposed. Please retry the question or cautiously increase TELEGRAM_AGENT_MAX_STEPS.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function localFallbackResult(input, context, preferredLanguage, trace, error) {
|
||||||
|
const german = preferredLanguage === 'de' || looksGerman(input);
|
||||||
|
const snapshot = parseSnapshot(context);
|
||||||
|
const reason = classifyFailure(error);
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
parts.push(german
|
||||||
|
? `Ich bekomme gerade keine stabile Modellantwort (${reason}). Ich nutze deshalb den letzten lokalen Sweep statt abzubrechen.`
|
||||||
|
: `I am not getting a stable model response right now (${reason}), so I am falling back to the last local sweep instead of going silent.`);
|
||||||
|
|
||||||
|
const news = Array.isArray(snapshot?.news) ? snapshot.news.map(item => item.title || item.headline).filter(Boolean).slice(0, 2) : [];
|
||||||
|
const urgent = Array.isArray(snapshot?.urgentOsint) ? snapshot.urgentOsint.filter(Boolean).slice(0, 1) : [];
|
||||||
|
const degraded = Array.isArray(snapshot?.degradedSources) ? snapshot.degradedSources.map(item => item.name).filter(Boolean).slice(0, 4) : [];
|
||||||
|
const ideas = Array.isArray(snapshot?.ideas) ? snapshot.ideas.map(item => item.title || item.ticker).filter(Boolean).slice(0, 2) : [];
|
||||||
|
|
||||||
|
if (news.length || urgent.length) {
|
||||||
|
parts.push(german
|
||||||
|
? `Im letzten Sweep fallen mir auf: ${[...urgent, ...news].join(' | ')}.`
|
||||||
|
: `From the last sweep, the main items I see are: ${[...urgent, ...news].join(' | ')}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ideas.length) {
|
||||||
|
parts.push(german
|
||||||
|
? `Marktseitig liegen diese Ideen im Snapshot: ${ideas.join(' | ')}.`
|
||||||
|
: `Market ideas in the snapshot: ${ideas.join(' | ')}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (degraded.length) {
|
||||||
|
parts.push(german
|
||||||
|
? `Einschraenkung: ${degraded.join(', ')} liefern gerade nicht sauber.`
|
||||||
|
: `Caveat: ${degraded.join(', ')} are not clean right now.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(german
|
||||||
|
? 'Steuern kann ich per Chat: Status, Quellen, Briefing, Maerkte, Evidenz/Websuche, Memory, Szenarien und nach deiner Bestaetigung Sweep oder Alerts.'
|
||||||
|
: 'From chat I can control status, sources, briefings, markets, evidence/web search, memory, scenarios, and after your confirmation sweeps or alerts.');
|
||||||
|
|
||||||
|
return {
|
||||||
|
answer: parts.filter(Boolean).join('\n\n'),
|
||||||
|
confidence: 'low',
|
||||||
|
evidence: [],
|
||||||
|
notify: false,
|
||||||
|
priority: 'routine',
|
||||||
|
trace,
|
||||||
|
degraded: 'llm_fallback',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSnapshot(context) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(String(context || '{}'));
|
||||||
|
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyFailure(error) {
|
||||||
|
const text = String(error?.message || error || '').toLowerCase();
|
||||||
|
if (text.includes('timeout') || text.includes('aborted')) return 'Timeout';
|
||||||
|
if (text.includes('fetch failed')) return 'Netzwerkfehler';
|
||||||
|
return 'LLM-Fehler';
|
||||||
|
}
|
||||||
|
|
||||||
|
function looksGerman(input) {
|
||||||
|
return /\b(wie|was|warum|angriff|russland|gefahr|bitte|ist|sind|kann|koennte|könnte|auch|andere|welt|passiert|pruef|prüf)\b/i.test(String(input || ''));
|
||||||
|
}
|
||||||
|
|
||||||
function finalResult(decision, trace) {
|
function finalResult(decision, trace) {
|
||||||
return {
|
return {
|
||||||
answer: String(decision.answer || '').trim() || 'No conclusion was produced.',
|
answer: String(decision.answer || '').trim() || 'No conclusion was produced.',
|
||||||
|
|||||||
@@ -611,10 +611,14 @@ export class TelegramAlerter {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Telegram] AI chat error:', err.message);
|
console.error('[Telegram] AI chat error:', err.message);
|
||||||
await this.sendMessage('AI chat failed. Please try again or use /status to check the LLM configuration.', {
|
await this.sendConversation([
|
||||||
|
'Dave bekommt gerade keine stabile Antwort vom Modell.',
|
||||||
|
'Ich bleibe erreichbar. Frag kurz nach Status, Quellen, Briefing, Maerkten, Websuche oder starte einen Sweep nach Bestaetigung.',
|
||||||
|
], {
|
||||||
chatId: replyChatId,
|
chatId: replyChatId,
|
||||||
replyToMessageId: msg.message_id,
|
replyToMessageId: msg.message_id,
|
||||||
parseMode: null,
|
parseMode: null,
|
||||||
|
maxChunkChars: 360,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
stopTyping();
|
stopTyping();
|
||||||
@@ -978,7 +982,7 @@ Respond with ONLY valid JSON:
|
|||||||
const signals = Array.isArray(evaluation.signals) ? evaluation.signals.map(compactLine).filter(Boolean).slice(0, 2) : [];
|
const signals = Array.isArray(evaluation.signals) ? evaluation.signals.map(compactLine).filter(Boolean).slice(0, 2) : [];
|
||||||
|
|
||||||
const messages = [
|
const messages = [
|
||||||
`[DAVE // ${tierLabel}]\n${headline}`,
|
german ? `Kurzer Hinweis: ${headline}` : `${tierLabel}: ${headline}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (reason && reason !== headline) {
|
if (reason && reason !== headline) {
|
||||||
|
|||||||
@@ -943,11 +943,8 @@ async function runProactiveAgent(data, delta) {
|
|||||||
}
|
}
|
||||||
const german = profile?.language === 'de';
|
const german = profile?.language === 'de';
|
||||||
const evidence = result.evidence?.length ? `${german ? 'Belege' : 'Evidence'}:\n${result.evidence.slice(0, 2).map(item => `- ${String(item).slice(0, 120)}`).join('\n')}` : '';
|
const evidence = result.evidence?.length ? `${german ? 'Belege' : 'Evidence'}:\n${result.evidence.slice(0, 2).map(item => `- ${String(item).slice(0, 120)}`).join('\n')}` : '';
|
||||||
const priority = german
|
|
||||||
? ({ routine: 'HINWEIS', priority: 'PRIORITÄT', flash: 'SOFORT' }[result.priority] || 'HINWEIS')
|
|
||||||
: String(result.priority || 'routine').toUpperCase();
|
|
||||||
const messages = [
|
const messages = [
|
||||||
`[DAVE // ${priority}]\n${result.answer}`,
|
result.answer,
|
||||||
evidence,
|
evidence,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
const sent = await telegramAlerter.sendConversation(messages, { maxChunkChars: 380 });
|
const sent = await telegramAlerter.sendConversation(messages, { maxChunkChars: 380 });
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ test('dynamic presence sends a grounded message and persists a variable next eva
|
|||||||
const now = new Date('2026-07-06T12:00:00Z');
|
const now = new Date('2026-07-06T12:00:00Z');
|
||||||
const outcome = await presence.tick(now);
|
const outcome = await presence.tick(now);
|
||||||
assert.deepEqual(outcome, { sent: true, reason: 'sent' });
|
assert.deepEqual(outcome, { sent: true, reason: 'sent' });
|
||||||
assert.match(messages[0], /^\[DAVE \/\/ AKTIV\]/);
|
assert.match(messages[0], /^Die Lage ist ruhig/);
|
||||||
|
assert.doesNotMatch(messages[0], /\[DAVE \/\/ AKTIV\]/);
|
||||||
assert.match(messages[0], /Belege:/);
|
assert.match(messages[0], /Belege:/);
|
||||||
assert.match(calls[0].prompt, /dynamic presence evaluation, not a fixed scheduled briefing/i);
|
assert.match(calls[0].prompt, /dynamic presence evaluation, not a fixed scheduled briefing/i);
|
||||||
assert.equal(calls[0].options.mode, 'presence');
|
assert.equal(calls[0].options.mode, 'presence');
|
||||||
|
|||||||
@@ -117,6 +117,41 @@ test('Telegram transport routes authorized free text as plain-text AI reply', as
|
|||||||
assert.equal(sent.body.reply_to_message_id, 11);
|
assert.equal(sent.body.reply_to_message_id, 11);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Telegram AI handler failures return short DAVE fallback messages', async () => {
|
||||||
|
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
||||||
|
const requests = [];
|
||||||
|
alerter.onMessage(async () => {
|
||||||
|
throw new Error('The operation was aborted due to timeout');
|
||||||
|
});
|
||||||
|
|
||||||
|
const originalFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = async (url, options = {}) => {
|
||||||
|
requests.push({ url, body: options.body ? JSON.parse(options.body) : null });
|
||||||
|
if (url.includes('/getUpdates')) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
json: async () => ({
|
||||||
|
ok: true,
|
||||||
|
result: [{ update_id: 1, message: { message_id: 10, text: 'Noch da?', chat: { id: 42 } } }],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ok: true, json: async () => ({ ok: true, result: { message_id: requests.length } }) };
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await alerter._pollUpdates();
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = originalFetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sent = requests.filter(request => request.url.includes('/sendMessage'));
|
||||||
|
assert.equal(sent.length, 2);
|
||||||
|
assert.match(sent[0].body.text, /Dave bekommt gerade keine stabile Antwort/);
|
||||||
|
assert.doesNotMatch(sent.map(item => item.body.text).join('\n'), /AI chat failed|Please try again/i);
|
||||||
|
assert.equal(sent[0].body.reply_to_message_id, 10);
|
||||||
|
});
|
||||||
|
|
||||||
test('Telegram conversation delivery strips visible markdown and splits large AI replies', async () => {
|
test('Telegram conversation delivery strips visible markdown and splits large AI replies', async () => {
|
||||||
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
||||||
const requests = [];
|
const requests = [];
|
||||||
@@ -209,8 +244,8 @@ test('Telegram tiered fallback alert is sent as conversational DAVE messages', a
|
|||||||
|
|
||||||
const texts = requests.filter(request => request.url.includes('/sendMessage')).map(request => request.body.text);
|
const texts = requests.filter(request => request.url.includes('/sendMessage')).map(request => request.body.text);
|
||||||
assert.ok(texts.length >= 2);
|
assert.ok(texts.length >= 2);
|
||||||
assert.match(texts[0], /^\[DAVE \/\/ HINWEIS\]/);
|
assert.match(texts[0], /^Kurzer Hinweis:/);
|
||||||
assert.doesNotMatch(texts.join('\n'), /CRUCIX ROUTINE|Confidence:|Direction:|Cross-correlation:|Werkzeuge|Tools used/);
|
assert.doesNotMatch(texts.join('\n'), /\[DAVE \/\/|CRUCIX ROUTINE|Confidence:|Direction:|Cross-correlation:|Werkzeuge|Tools used/);
|
||||||
for (const text of texts) assert.ok(text.length <= 380, `alert chunk too long: ${text.length}`);
|
for (const text of texts) assert.ok(text.length <= 380, `alert chunk too long: ${text.length}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,52 @@ test('repeated tool calls during finalization fail closed without leaking protoc
|
|||||||
assert.equal(result.notify, false);
|
assert.equal(result.notify, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('empty model responses are repaired instead of shown to the operator', async () => {
|
||||||
|
let call = 0;
|
||||||
|
const agent = new TerminalAgent({
|
||||||
|
provider: {
|
||||||
|
isConfigured: true,
|
||||||
|
async complete() {
|
||||||
|
call++;
|
||||||
|
return call === 1
|
||||||
|
? { text: '' }
|
||||||
|
: { text: JSON.stringify({ type: 'final', answer: 'Ich pruefe den letzten Sweep weiter.', confidence: 'low', evidence: [], notify: false, priority: 'routine' }) };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registry: new TerminalToolRegistry([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await agent.run('Ist noch woanders was passiert?', { preferredLanguage: 'de' });
|
||||||
|
assert.equal(result.answer, 'Ich pruefe den letzten Sweep weiter.');
|
||||||
|
assert.doesNotMatch(result.answer, /no usable response/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('provider timeout falls back to the local sweep snapshot', async () => {
|
||||||
|
const agent = new TerminalAgent({
|
||||||
|
provider: {
|
||||||
|
isConfigured: true,
|
||||||
|
async complete() {
|
||||||
|
throw new Error('The operation was aborted due to timeout');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registry: new TerminalToolRegistry([]),
|
||||||
|
});
|
||||||
|
const context = JSON.stringify({
|
||||||
|
news: [{ title: 'Regional escalation report' }],
|
||||||
|
urgentOsint: ['OSINT: power outage near border'],
|
||||||
|
degradedSources: [{ name: 'GDELT' }, { name: 'ACLED' }],
|
||||||
|
ideas: [{ title: 'Gold hedge' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await agent.run('Auch auf andere Teile der Welt', { preferredLanguage: 'de', context });
|
||||||
|
assert.match(result.answer, /keine stabile Modellantwort/);
|
||||||
|
assert.match(result.answer, /letzten lokalen Sweep/);
|
||||||
|
assert.match(result.answer, /OSINT: power outage near border/);
|
||||||
|
assert.match(result.answer, /Steuern kann ich per Chat/);
|
||||||
|
assert.equal(result.trace[0].tool, 'llm');
|
||||||
|
assert.equal(result.trace[0].status, 'failed');
|
||||||
|
});
|
||||||
|
|
||||||
test('agent propagates preferred German language through tool and finalization prompts', async () => {
|
test('agent propagates preferred German language through tool and finalization prompts', async () => {
|
||||||
const prompts = [];
|
const prompts = [];
|
||||||
let call = 0;
|
let call = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user