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 { FetchService } from '@ghostfolio/api/services/fetch/fetch.service';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
@ -6,7 +5,7 @@ import { Module } from '@nestjs/common';
@Module({ @Module({
exports: [FetchService], exports: [FetchService],
imports: [ConfigurationModule, PropertyModule], imports: [PropertyModule],
providers: [FetchService] providers: [FetchService]
}) })
export class FetchModule {} export class FetchModule {}

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

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