feat(CC-054): add ExternalDataService (legislation + AnythingLLM + capability)
This commit is contained in:
parent
3a39adb4bc
commit
aefd370d78
1 changed files with 81 additions and 0 deletions
81
src/external-data/external-data.service.ts
Normal file
81
src/external-data/external-data.service.ts
Normal 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 }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue