feat(CC-054): add ExternalDataService (legislation + AnythingLLM + capability)

This commit is contained in:
admin-valentin 2026-08-01 12:32:02 +00:00
parent 3a39adb4bc
commit aefd370d78

View file

@ -0,0 +1,81 @@
import { BadGatewayException, Injectable } from '@nestjs/common';
const TIMEOUT_MS = 10_000;
async function jsonFetch<T>(url: string, opts: RequestInit = {}): Promise<T> {
const ctrl = new AbortController();
const id = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
try {
const res = await fetch(url, {
...opts,
headers: { 'Content-Type': 'application/json', ...opts.headers },
signal: ctrl.signal,
});
clearTimeout(id);
if (!res.ok) throw new BadGatewayException(`upstream returned ${res.status}`);
return (await res.json()) as T;
} catch (err) {
clearTimeout(id);
if (err instanceof BadGatewayException) throw err;
throw new BadGatewayException('upstream service unreachable');
}
}
@Injectable()
export class ExternalDataService {
/** ceo-os-legislation-api: vector search over imported legislation corpus */
private readonly legislationUrl =
process.env.LEGISLATION_API_URL ?? 'http://91.98.39.120:9404';
/** AnythingLLM: AI-interpreted legislation answers via agent workspace */
private readonly anythingLlmUrl =
process.env.ANYTHINGLLM_URL ?? 'http://152.53.112.35:3010';
private readonly anythingLlmKey =
process.env.ANYTHINGLLM_API_KEY ?? '';
private readonly legislationWorkspace =
process.env.ANYTHINGLLM_LEGISLATION_WORKSPACE ?? 'legislation';
/**
* Raw vector search: returns matching legislation sections (fast, no AI).
* The 'scenario' field must be a full situation description, not just a keyword.
*/
async searchLegislation(scenario: string, country?: string, topK = 8) {
return jsonFetch(`${this.legislationUrl}/search_legislation`, {
method: 'POST',
body: JSON.stringify({ scenario, country: country ?? null, top_k: topK }),
});
}
/**
* AI-interpreted legislation answer via AnythingLLM agent workspace.
* Returns a conversational answer grounded in the legislation corpus.
*/
async askLegislationAgent(message: string, sessionId?: string) {
if (!this.anythingLlmKey) {
throw new BadGatewayException('ANYTHINGLLM_API_KEY not configured');
}
return jsonFetch(
`${this.anythingLlmUrl}/api/v1/workspace/${this.legislationWorkspace}/chat`,
{
method: 'POST',
headers: { Authorization: `Bearer ${this.anythingLlmKey}` },
body: JSON.stringify({
message,
mode: 'chat',
sessionId: sessionId ?? 'ceo-os-legislation',
attachments: [],
}),
},
);
}
async checkCapability(capability: string, context?: string) {
const capUrl = process.env.CAPABILITY_API_URL ?? 'http://91.98.39.120:9402';
return jsonFetch(`${capUrl}/check_capability`, {
method: 'POST',
body: JSON.stringify({ capability, context }),
});
}
}