Browse Source

Refactoring

pull/6960/head
Thomas Kaul 3 months ago
parent
commit
e71089101c
  1. 3
      apps/api/src/services/fetch/fetch.module.ts
  2. 115
      apps/api/src/services/fetch/fetch.service.ts

3
apps/api/src/services/fetch/fetch.module.ts

@ -1,4 +1,3 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { FetchService } from '@ghostfolio/api/services/fetch/fetch.service';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
@ -6,7 +5,7 @@ import { Module } from '@nestjs/common';
@Module({
exports: [FetchService],
imports: [ConfigurationModule, PropertyModule],
imports: [PropertyModule],
providers: [FetchService]
})
export class FetchModule {}

115
apps/api/src/services/fetch/fetch.service.ts

@ -8,6 +8,7 @@ import {
import { Injectable, Logger } from '@nestjs/common';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText, jsonSchema, tool } from 'ai';
import ms from 'ms';
@Injectable()
export class FetchService {
@ -59,56 +60,6 @@ export class FetchService {
}
}
private extractJsonFromWebFetchResult({
response,
sources,
text
}: {
response: { body?: unknown };
sources: { providerMetadata?: Record<string, Record<string, unknown>> }[];
text: string;
}): string | undefined {
const candidates: string[] = [];
for (const source of sources ?? []) {
const content = source?.providerMetadata?.openrouter?.content;
if (typeof content === 'string' && content) {
candidates.push(content);
}
}
const body = response?.body as
| {
choices?: {
message?: {
annotations?: { url_citation?: { content?: string } }[];
};
}[];
}
| undefined;
for (const annotation of body?.choices?.[0]?.message?.annotations ?? []) {
if (annotation?.url_citation?.content) {
candidates.push(annotation.url_citation.content);
}
}
if (text) {
candidates.push(text);
}
for (const candidate of candidates) {
const sanitized = this.sanitizeJson(candidate);
if (sanitized) {
return sanitized;
}
}
return undefined;
}
private async fetchViaWebFetchTool(
url: string
): Promise<Response | undefined> {
@ -124,11 +75,12 @@ export class FetchService {
try {
const openRouterService = createOpenRouter({ apiKey: openRouterApiKey });
const { response, sources, text } = await generateText({
const { sources, text } = await generateText({
abortSignal: AbortSignal.timeout(ms('30 seconds')),
model: openRouterService.chat(openRouterModel),
prompt: [
'Fetch the following URL and return its response body exactly as received.',
'Respond with the raw body only (no commentary, no Markdown, and no code fences).',
'You have access to a web_fetch tool. You MUST call it to retrieve the URL below, do not answer from prior knowledge.',
'Return the fetched response body exactly as received: raw body only, no commentary, no Markdown, and no code fences.',
`URL: ${url}`
].join('\n'),
tools: {
@ -144,24 +96,37 @@ export class FetchService {
}
});
const body = this.extractJsonFromWebFetchResult({
response,
sources,
const candidates = [
...sources.map((source) => {
return source.providerMetadata?.openrouter?.content;
}),
text
});
];
if (!body) {
return undefined;
}
for (const candidate of candidates) {
if (typeof candidate !== 'string') {
continue;
}
Logger.debug(
`Routed ${this.redactUrl(url)} via web fetch tool`,
'FetchService'
);
const body = candidate.trim();
return new Response(body, {
headers: { 'content-type': 'application/json' }
});
try {
JSON.parse(body);
} catch {
continue;
}
Logger.debug(
`Routed ${this.redactUrl(url)} via web fetch tool`,
'FetchService'
);
return new Response(body, {
headers: { 'content-type': 'application/json' }
});
}
return undefined;
} catch (error) {
Logger.error(
`Web fetch tool failed for ${this.redactUrl(url)}: ${
@ -206,20 +171,4 @@ export class FetchService {
return rawUrl;
}
}
private sanitizeJson(value: string): string | undefined {
const sanitized = value
.trim()
.replace(/^```(?:json)?/i, '')
.replace(/```$/, '')
.trim();
try {
JSON.parse(sanitized);
return sanitized;
} catch {
return undefined;
}
}
}

Loading…
Cancel
Save