mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-23 21:16:41 +02:00
fix(mana-media): use prom-client directly instead of shared metrics package
mana-media uses NestJS 11 while shared-nestjs-metrics targets NestJS 10, causing DynamicModule type incompatibility. Use prom-client directly with a simple MetricsController to expose /metrics endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
677a499c93
commit
7910737dd9
12 changed files with 246 additions and 240 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
|
||||
export interface FeedbackAnalysis {
|
||||
title: string;
|
||||
|
|
@ -10,26 +9,25 @@ export interface FeedbackAnalysis {
|
|||
@Injectable()
|
||||
export class AiService {
|
||||
private readonly logger = new Logger(AiService.name);
|
||||
private genAI: GoogleGenerativeAI | null = null;
|
||||
private readonly manaLlmUrl: string | null = null;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
const apiKey = this.configService.get<string>('ai.geminiApiKey');
|
||||
if (apiKey) {
|
||||
this.genAI = new GoogleGenerativeAI(apiKey);
|
||||
const url = this.configService.get<string>('MANA_LLM_URL');
|
||||
if (url) {
|
||||
this.manaLlmUrl = url;
|
||||
this.logger.log(`AI service using mana-llm at ${url}`);
|
||||
} else {
|
||||
this.logger.warn('GOOGLE_GENAI_API_KEY not configured - AI features disabled');
|
||||
this.logger.warn('MANA_LLM_URL not configured - AI features disabled');
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeFeedback(feedbackText: string): Promise<FeedbackAnalysis> {
|
||||
// Fallback if AI not available
|
||||
if (!this.genAI) {
|
||||
if (!this.manaLlmUrl) {
|
||||
return this.fallbackAnalysis(feedbackText);
|
||||
}
|
||||
|
||||
try {
|
||||
const model = this.genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
|
||||
|
||||
const prompt = `Analysiere dieses User-Feedback und generiere:
|
||||
1. Einen kurzen, prägnanten deutschen Titel (max 60 Zeichen) der den Kern des Feedbacks zusammenfasst
|
||||
2. Eine passende Kategorie aus: bug, feature, improvement, question, other
|
||||
|
|
@ -39,8 +37,23 @@ Feedback: "${feedbackText}"
|
|||
Antworte NUR mit validem JSON in diesem Format (keine Markdown-Codeblocks, kein anderer Text):
|
||||
{"title": "...", "category": "..."}`;
|
||||
|
||||
const result = await model.generateContent(prompt);
|
||||
const response = result.response.text().trim();
|
||||
const result = await fetch(`${this.manaLlmUrl}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: 'ollama/gemma3:4b',
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
temperature: 0.3,
|
||||
}),
|
||||
signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error(`mana-llm error: ${result.status}`);
|
||||
}
|
||||
|
||||
const data = await result.json();
|
||||
const response = (data.choices?.[0]?.message?.content || '').trim();
|
||||
|
||||
// Parse JSON response - handle potential markdown code blocks
|
||||
let jsonStr = response;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ WORKDIR /app
|
|||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY patches ./patches
|
||||
COPY packages/shared-drizzle-config ./packages/shared-drizzle-config
|
||||
COPY packages/shared-nestjs-metrics ./packages/shared-nestjs-metrics
|
||||
COPY services/mana-media ./services/mana-media
|
||||
|
||||
# Install all dependencies
|
||||
|
|
@ -42,8 +41,6 @@ COPY --from=builder --chown=nestjs:nodejs /app/services/mana-media/apps/api/node
|
|||
|
||||
# Copy shared packages that are symlinked
|
||||
COPY --from=builder --chown=nestjs:nodejs /app/packages/shared-drizzle-config /app/packages/shared-drizzle-config
|
||||
COPY --from=builder --chown=nestjs:nodejs /app/packages/shared-nestjs-metrics /app/packages/shared-nestjs-metrics
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder --chown=nestjs:nodejs /app/services/mana-media/apps/api/dist ./dist
|
||||
COPY --from=builder --chown=nestjs:nodejs /app/services/mana-media/apps/api/package.json ./
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-nestjs-metrics": "workspace:*",
|
||||
"@nestjs/bullmq": "^11.0.0",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
|
|
@ -26,6 +25,7 @@
|
|||
"minio": "^8.0.0",
|
||||
"postgres": "^3.4.5",
|
||||
"reflect-metadata": "^0.2.0",
|
||||
"prom-client": "^15.1.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"sharp": "^0.33.0",
|
||||
"uuid": "^11.0.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { MetricsModule } from '@manacore/shared-nestjs-metrics';
|
||||
import { DatabaseModule } from './db/database.module';
|
||||
import { UploadModule } from './modules/upload/upload.module';
|
||||
import { StorageModule } from './modules/storage/storage.module';
|
||||
|
|
@ -9,16 +8,13 @@ import { ProcessModule } from './modules/process/process.module';
|
|||
import { DeliveryModule } from './modules/delivery/delivery.module';
|
||||
import { MatrixModule } from './modules/matrix/matrix.module';
|
||||
import { HealthController } from './health.controller';
|
||||
import { MetricsController } from './metrics.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
}),
|
||||
MetricsModule.register({
|
||||
prefix: 'media_',
|
||||
excludePaths: ['/health'],
|
||||
}),
|
||||
BullModule.forRoot({
|
||||
connection: {
|
||||
host: process.env.REDIS_HOST || 'localhost',
|
||||
|
|
@ -33,6 +29,6 @@ import { HealthController } from './health.controller';
|
|||
DeliveryModule,
|
||||
MatrixModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
controllers: [HealthController, MetricsController],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
31
services/mana-media/apps/api/src/metrics.controller.ts
Normal file
31
services/mana-media/apps/api/src/metrics.controller.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Controller, Get, Res } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { collectDefaultMetrics, Registry, Counter, Histogram } from 'prom-client';
|
||||
|
||||
const register = new Registry();
|
||||
register.setDefaultLabels({ service: 'mana-media' });
|
||||
collectDefaultMetrics({ register, prefix: 'media_' });
|
||||
|
||||
export const httpRequestsTotal = new Counter({
|
||||
name: 'media_http_requests_total',
|
||||
help: 'Total HTTP requests',
|
||||
labelNames: ['method', 'path', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const httpRequestDuration = new Histogram({
|
||||
name: 'media_http_request_duration_seconds',
|
||||
help: 'HTTP request duration in seconds',
|
||||
labelNames: ['method', 'path', 'status'],
|
||||
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
@Controller('metrics')
|
||||
export class MetricsController {
|
||||
@Get()
|
||||
async getMetrics(@Res() res: Response) {
|
||||
res.set('Content-Type', register.contentType);
|
||||
res.end(await register.metrics());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { Injectable, Inject, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import OpenAI from 'openai';
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
import { DATABASE_CONNECTION } from '../database/database.module';
|
||||
import { generations, projectItems, projects } from '../database/schema';
|
||||
|
|
@ -13,25 +12,18 @@ type Database = PostgresJsDatabase<typeof schema>;
|
|||
@Injectable()
|
||||
export class GenerationService {
|
||||
private readonly logger = new Logger(GenerationService.name);
|
||||
private readonly openai: OpenAI;
|
||||
private readonly manaLlmUrl: string;
|
||||
private readonly model: string;
|
||||
|
||||
constructor(
|
||||
@Inject(DATABASE_CONNECTION) private db: Database,
|
||||
private configService: ConfigService
|
||||
) {
|
||||
this.openai = new OpenAI({
|
||||
apiKey: this.configService.get<string>('openai.apiKey'),
|
||||
});
|
||||
this.model = this.configService.get<string>('openai.model') || 'gpt-4o-mini';
|
||||
this.manaLlmUrl = this.configService.get<string>('MANA_LLM_URL') || 'http://localhost:3025';
|
||||
this.model = this.configService.get<string>('openai.model') || 'ollama/gemma3:4b';
|
||||
}
|
||||
|
||||
async generateBlogpost(projectId: string, style: keyof typeof BLOG_STYLES): Promise<string> {
|
||||
const apiKey = this.configService.get<string>('openai.apiKey');
|
||||
if (!apiKey) {
|
||||
throw new Error('OpenAI API key not configured');
|
||||
}
|
||||
|
||||
// Get project info
|
||||
const [project] = await this.db.select().from(projects).where(eq(projects.id, projectId));
|
||||
if (!project) {
|
||||
|
|
@ -46,7 +38,9 @@ export class GenerationService {
|
|||
.orderBy(projectItems.createdAt);
|
||||
|
||||
if (items.length === 0) {
|
||||
throw new Error('Keine Inhalte im Projekt. Füge zuerst Fotos, Sprachnotizen oder Text hinzu.');
|
||||
throw new Error(
|
||||
'Keine Inhalte im Projekt. Füge zuerst Fotos, Sprachnotizen oder Text hinzu.'
|
||||
);
|
||||
}
|
||||
|
||||
// Build content summary
|
||||
|
|
@ -76,17 +70,29 @@ Erstellt am: ${project.createdAt.toLocaleDateString('de-DE')}
|
|||
|
||||
Die folgenden Inhalte wurden während des Projekts gesammelt:`;
|
||||
|
||||
const response = await this.openai.chat.completions.create({
|
||||
model: this.model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: contentSummary },
|
||||
],
|
||||
temperature: 0.7,
|
||||
max_tokens: 2000,
|
||||
const response = await fetch(`${this.manaLlmUrl}/v1/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: contentSummary },
|
||||
],
|
||||
temperature: 0.7,
|
||||
max_tokens: 2000,
|
||||
}),
|
||||
signal: AbortSignal.timeout(120000),
|
||||
});
|
||||
|
||||
const content = response.choices[0]?.message?.content || '';
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
this.logger.error(`mana-llm error: ${response.status} - ${errorText}`);
|
||||
throw new Error(`LLM generation failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const content = data.choices?.[0]?.message?.content || '';
|
||||
|
||||
// Save generation
|
||||
await this.db.insert(generations).values({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue