From 5d7c3946500135e3dfedc136e8875896f8e18c02 Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Sun, 11 Jan 2026 15:30:23 +0000 Subject: [PATCH] diff models for diff work --- orchestrator/src/client/api/client.ts | 3 + .../src/client/pages/SettingsPage.tsx | 91 ++++++++++++++++++- orchestrator/src/server/api/routes.ts | 44 +++++++++ orchestrator/src/server/repositories/jobs.ts | 2 + .../src/server/repositories/settings.ts | 3 + .../src/server/services/projectSelection.ts | 4 +- orchestrator/src/server/services/scorer.ts | 4 +- orchestrator/src/server/services/summary.ts | 7 +- orchestrator/src/shared/types.ts | 8 ++ 9 files changed, 162 insertions(+), 4 deletions(-) diff --git a/orchestrator/src/client/api/client.ts b/orchestrator/src/client/api/client.ts index 2bf3373..572e7ae 100644 --- a/orchestrator/src/client/api/client.ts +++ b/orchestrator/src/client/api/client.ts @@ -143,6 +143,9 @@ export async function getProfileProjects(): Promise export async function updateSettings(update: { model?: string | null + modelScorer?: string | null + modelTailoring?: string | null + modelProjectSelection?: string | null pipelineWebhookUrl?: string | null jobCompleteWebhookUrl?: string | null resumeProjects?: ResumeProjectsSettings | null diff --git a/orchestrator/src/client/pages/SettingsPage.tsx b/orchestrator/src/client/pages/SettingsPage.tsx index 25f5c3c..1e759f6 100644 --- a/orchestrator/src/client/pages/SettingsPage.tsx +++ b/orchestrator/src/client/pages/SettingsPage.tsx @@ -57,6 +57,9 @@ function clampInt(value: number, min: number, max: number) { export const SettingsPage: React.FC = () => { const [settings, setSettings] = useState(null) const [modelDraft, setModelDraft] = useState("") + const [modelScorerDraft, setModelScorerDraft] = useState("") + const [modelTailoringDraft, setModelTailoringDraft] = useState("") + const [modelProjectSelectionDraft, setModelProjectSelectionDraft] = useState("") const [pipelineWebhookUrlDraft, setPipelineWebhookUrlDraft] = useState("") const [jobCompleteWebhookUrlDraft, setJobCompleteWebhookUrlDraft] = useState("") const [resumeProjectsDraft, setResumeProjectsDraft] = useState(null) @@ -79,6 +82,9 @@ export const SettingsPage: React.FC = () => { if (!isMounted) return setSettings(data) setModelDraft(data.overrideModel ?? "") + setModelScorerDraft(data.overrideModelScorer ?? "") + setModelTailoringDraft(data.overrideModelTailoring ?? "") + setModelProjectSelectionDraft(data.overrideModelProjectSelection ?? "") setPipelineWebhookUrlDraft(data.overridePipelineWebhookUrl ?? "") setJobCompleteWebhookUrlDraft(data.overrideJobCompleteWebhookUrl ?? "") setResumeProjectsDraft(data.resumeProjects) @@ -107,6 +113,12 @@ export const SettingsPage: React.FC = () => { const effectiveModel = settings?.model ?? "" const defaultModel = settings?.defaultModel ?? "" const overrideModel = settings?.overrideModel + const effectiveModelScorer = settings?.modelScorer ?? "" + const overrideModelScorer = settings?.overrideModelScorer + const effectiveModelTailoring = settings?.modelTailoring ?? "" + const overrideModelTailoring = settings?.overrideModelTailoring + const effectiveModelProjectSelection = settings?.modelProjectSelection ?? "" + const overrideModelProjectSelection = settings?.overrideModelProjectSelection const effectivePipelineWebhookUrl = settings?.pipelineWebhookUrl ?? "" const defaultPipelineWebhookUrl = settings?.defaultPipelineWebhookUrl ?? "" const overridePipelineWebhookUrl = settings?.overridePipelineWebhookUrl @@ -142,6 +154,12 @@ export const SettingsPage: React.FC = () => { if (!settings || !resumeProjectsDraft) return false const next = modelDraft.trim() const current = (overrideModel ?? "").trim() + const nextScorer = modelScorerDraft.trim() + const currentScorer = (overrideModelScorer ?? "").trim() + const nextTailoring = modelTailoringDraft.trim() + const currentTailoring = (overrideModelTailoring ?? "").trim() + const nextProjectSelection = modelProjectSelectionDraft.trim() + const currentProjectSelection = (overrideModelProjectSelection ?? "").trim() const nextWebhook = pipelineWebhookUrlDraft.trim() const currentWebhook = (overridePipelineWebhookUrl ?? "").trim() const nextJobCompleteWebhook = jobCompleteWebhookUrlDraft.trim() @@ -150,6 +168,9 @@ export const SettingsPage: React.FC = () => { const searchTermsChanged = JSON.stringify(searchTermsDraft) !== JSON.stringify(overrideSearchTerms ?? null) return ( next !== current || + nextScorer !== currentScorer || + nextTailoring !== currentTailoring || + nextProjectSelection !== currentProjectSelection || nextWebhook !== currentWebhook || nextJobCompleteWebhook !== currentJobCompleteWebhook || !resumeProjectsEqual(resumeProjectsDraft, settings.resumeProjects) || @@ -164,9 +185,15 @@ export const SettingsPage: React.FC = () => { }, [ settings, modelDraft, + modelScorerDraft, + modelTailoringDraft, + modelProjectSelectionDraft, pipelineWebhookUrlDraft, jobCompleteWebhookUrlDraft, overrideModel, + overrideModelScorer, + overrideModelTailoring, + overrideModelProjectSelection, overridePipelineWebhookUrl, overrideJobCompleteWebhookUrl, resumeProjectsDraft, @@ -191,6 +218,9 @@ export const SettingsPage: React.FC = () => { try { setIsSaving(true) const trimmed = modelDraft.trim() + const trimmedScorer = modelScorerDraft.trim() + const trimmedTailoring = modelTailoringDraft.trim() + const trimmedProjectSelection = modelProjectSelectionDraft.trim() const webhookTrimmed = pipelineWebhookUrlDraft.trim() const jobCompleteTrimmed = jobCompleteWebhookUrlDraft.trim() const resumeProjectsOverride = resumeProjectsEqual(resumeProjectsDraft, settings.defaultResumeProjects) @@ -205,6 +235,9 @@ export const SettingsPage: React.FC = () => { const jobspyLinkedinFetchDescriptionOverride = jobspyLinkedinFetchDescriptionDraft === defaultJobspyLinkedinFetchDescription ? null : jobspyLinkedinFetchDescriptionDraft const updated = await api.updateSettings({ model: trimmed.length > 0 ? trimmed : null, + modelScorer: trimmedScorer.length > 0 ? trimmedScorer : null, + modelTailoring: trimmedTailoring.length > 0 ? trimmedTailoring : null, + modelProjectSelection: trimmedProjectSelection.length > 0 ? trimmedProjectSelection : null, pipelineWebhookUrl: webhookTrimmed.length > 0 ? webhookTrimmed : null, jobCompleteWebhookUrl: jobCompleteTrimmed.length > 0 ? jobCompleteTrimmed : null, resumeProjects: resumeProjectsOverride, @@ -218,6 +251,9 @@ export const SettingsPage: React.FC = () => { }) setSettings(updated) setModelDraft(updated.overrideModel ?? "") + setModelScorerDraft(updated.overrideModelScorer ?? "") + setModelTailoringDraft(updated.overrideModelTailoring ?? "") + setModelProjectSelectionDraft(updated.overrideModelProjectSelection ?? "") setPipelineWebhookUrlDraft(updated.overridePipelineWebhookUrl ?? "") setJobCompleteWebhookUrlDraft(updated.overrideJobCompleteWebhookUrl ?? "") setResumeProjectsDraft(updated.resumeProjects) @@ -266,6 +302,9 @@ export const SettingsPage: React.FC = () => { setIsSaving(true) const updated = await api.updateSettings({ model: null, + modelScorer: null, + modelTailoring: null, + modelProjectSelection: null, pipelineWebhookUrl: null, jobCompleteWebhookUrl: null, resumeProjects: null, @@ -279,6 +318,9 @@ export const SettingsPage: React.FC = () => { }) setSettings(updated) setModelDraft("") + setModelScorerDraft("") + setModelTailoringDraft("") + setModelProjectSelectionDraft("") setPipelineWebhookUrlDraft("") setJobCompleteWebhookUrlDraft("") setResumeProjectsDraft(updated.resumeProjects) @@ -327,9 +369,56 @@ export const SettingsPage: React.FC = () => { +
+
Task-Specific Overrides
+ +
+
+
Scoring Model
+ setModelScorerDraft(event.target.value)} + placeholder={effectiveModel || "inherit"} + disabled={isLoading || isSaving} + /> +
+ Effective: {effectiveModelScorer || effectiveModel} +
+
+ +
+
Tailoring Model
+ setModelTailoringDraft(event.target.value)} + placeholder={effectiveModel || "inherit"} + disabled={isLoading || isSaving} + /> +
+ Effective: {effectiveModelTailoring || effectiveModel} +
+
+ +
+
Project Selection Model
+ setModelProjectSelectionDraft(event.target.value)} + placeholder={effectiveModel || "inherit"} + disabled={isLoading || isSaving} + /> +
+ Effective: {effectiveModelProjectSelection || effectiveModel} +
+
+
+
+ + +
-
Effective
+
Global Effective
{effectiveModel || "—"}
diff --git a/orchestrator/src/server/api/routes.ts b/orchestrator/src/server/api/routes.ts index 7797415..6a61782 100644 --- a/orchestrator/src/server/api/routes.ts +++ b/orchestrator/src/server/api/routes.ts @@ -270,6 +270,16 @@ apiRouter.get('/settings', async (_req: Request, res: Response) => { const defaultModel = process.env.MODEL || 'openai/gpt-4o-mini'; const model = overrideModel || defaultModel; + // Specific AI models + const overrideModelScorer = await settingsRepo.getSetting('modelScorer'); + const modelScorer = overrideModelScorer || model; + + const overrideModelTailoring = await settingsRepo.getSetting('modelTailoring'); + const modelTailoring = overrideModelTailoring || model; + + const overrideModelProjectSelection = await settingsRepo.getSetting('modelProjectSelection'); + const modelProjectSelection = overrideModelProjectSelection || model; + const overridePipelineWebhookUrl = await settingsRepo.getSetting('pipelineWebhookUrl'); const defaultPipelineWebhookUrl = process.env.PIPELINE_WEBHOOK_URL || process.env.WEBHOOK_URL || ''; const pipelineWebhookUrl = overridePipelineWebhookUrl || defaultPipelineWebhookUrl; @@ -326,6 +336,12 @@ apiRouter.get('/settings', async (_req: Request, res: Response) => { model, defaultModel, overrideModel, + modelScorer, + overrideModelScorer, + modelTailoring, + overrideModelTailoring, + modelProjectSelection, + overrideModelProjectSelection, pipelineWebhookUrl, defaultPipelineWebhookUrl, overridePipelineWebhookUrl, @@ -364,6 +380,9 @@ apiRouter.get('/settings', async (_req: Request, res: Response) => { const updateSettingsSchema = z.object({ model: z.string().trim().min(1).max(200).nullable().optional(), + modelScorer: z.string().trim().min(1).max(200).nullable().optional(), + modelTailoring: z.string().trim().min(1).max(200).nullable().optional(), + modelProjectSelection: z.string().trim().min(1).max(200).nullable().optional(), pipelineWebhookUrl: z.string().trim().min(1).max(2000).nullable().optional(), jobCompleteWebhookUrl: z.string().trim().min(1).max(2000).nullable().optional(), resumeProjects: z.object({ @@ -392,6 +411,16 @@ apiRouter.patch('/settings', async (req: Request, res: Response) => { await settingsRepo.setSetting('model', model); } + if ('modelScorer' in input) { + await settingsRepo.setSetting('modelScorer', input.modelScorer ?? null); + } + if ('modelTailoring' in input) { + await settingsRepo.setSetting('modelTailoring', input.modelTailoring ?? null); + } + if ('modelProjectSelection' in input) { + await settingsRepo.setSetting('modelProjectSelection', input.modelProjectSelection ?? null); + } + if ('pipelineWebhookUrl' in input) { const pipelineWebhookUrl = input.pipelineWebhookUrl ?? null; await settingsRepo.setSetting('pipelineWebhookUrl', pipelineWebhookUrl); @@ -455,6 +484,15 @@ apiRouter.patch('/settings', async (req: Request, res: Response) => { const defaultModel = process.env.MODEL || 'openai/gpt-4o-mini'; const model = overrideModel || defaultModel; + const overrideModelScorer = await settingsRepo.getSetting('modelScorer'); + const modelScorer = overrideModelScorer || model; + + const overrideModelTailoring = await settingsRepo.getSetting('modelTailoring'); + const modelTailoring = overrideModelTailoring || model; + + const overrideModelProjectSelection = await settingsRepo.getSetting('modelProjectSelection'); + const modelProjectSelection = overrideModelProjectSelection || model; + const overridePipelineWebhookUrl = await settingsRepo.getSetting('pipelineWebhookUrl'); const defaultPipelineWebhookUrl = process.env.PIPELINE_WEBHOOK_URL || process.env.WEBHOOK_URL || ''; const pipelineWebhookUrl = overridePipelineWebhookUrl || defaultPipelineWebhookUrl; @@ -512,6 +550,12 @@ apiRouter.patch('/settings', async (req: Request, res: Response) => { model, defaultModel, overrideModel, + modelScorer, + overrideModelScorer, + modelTailoring, + overrideModelTailoring, + modelProjectSelection, + overrideModelProjectSelection, pipelineWebhookUrl, defaultPipelineWebhookUrl, overridePipelineWebhookUrl, diff --git a/orchestrator/src/server/repositories/jobs.ts b/orchestrator/src/server/repositories/jobs.ts index d762835..0da519c 100644 --- a/orchestrator/src/server/repositories/jobs.ts +++ b/orchestrator/src/server/repositories/jobs.ts @@ -241,6 +241,8 @@ function mapRowToJob(row: typeof jobs.$inferSelect): Job { suitabilityScore: row.suitabilityScore, suitabilityReason: row.suitabilityReason, tailoredSummary: row.tailoredSummary, + tailoredHeadline: row.tailoredHeadline ?? null, + tailoredSkills: row.tailoredSkills ?? null, selectedProjectIds: row.selectedProjectIds ?? null, pdfPath: row.pdfPath, notionPageId: row.notionPageId, diff --git a/orchestrator/src/server/repositories/settings.ts b/orchestrator/src/server/repositories/settings.ts index 2d260b1..c22c228 100644 --- a/orchestrator/src/server/repositories/settings.ts +++ b/orchestrator/src/server/repositories/settings.ts @@ -8,6 +8,9 @@ import { db, schema } from '../db/index.js' const { settings } = schema export type SettingKey = 'model' + | 'modelScorer' + | 'modelTailoring' + | 'modelProjectSelection' | 'pipelineWebhookUrl' | 'jobCompleteWebhookUrl' | 'resumeProjects' diff --git a/orchestrator/src/server/services/projectSelection.ts b/orchestrator/src/server/services/projectSelection.ts index f2cbc76..1fd405f 100644 --- a/orchestrator/src/server/services/projectSelection.ts +++ b/orchestrator/src/server/services/projectSelection.ts @@ -21,7 +21,9 @@ export async function pickProjectIdsForJob(args: { } const overrideModel = await getSetting('model'); - const model = overrideModel || process.env.MODEL || 'openai/gpt-4o-mini'; + const overrideModelProjectSelection = await getSetting('modelProjectSelection'); + // Precedence: Project-specific override > Global override > Env var > Default + const model = overrideModelProjectSelection || overrideModel || process.env.MODEL || 'openai/gpt-4o-mini'; const prompt = buildProjectSelectionPrompt({ jobDescription: args.jobDescription, diff --git a/orchestrator/src/server/services/scorer.ts b/orchestrator/src/server/services/scorer.ts index afd04fc..4c7e18d 100644 --- a/orchestrator/src/server/services/scorer.ts +++ b/orchestrator/src/server/services/scorer.ts @@ -26,7 +26,9 @@ export async function scoreJobSuitability( } const overrideModel = await getSetting('model'); - const model = overrideModel || process.env.MODEL || 'openai/gpt-4o-mini'; + const overrideModelScorer = await getSetting('modelScorer'); + // Precedence: Scorer-specific override > Global override > Env var > Default + const model = overrideModelScorer || overrideModel || process.env.MODEL || 'openai/gpt-4o-mini'; const prompt = buildScoringPrompt(job, profile); diff --git a/orchestrator/src/server/services/summary.ts b/orchestrator/src/server/services/summary.ts index 8359068..fc70590 100644 --- a/orchestrator/src/server/services/summary.ts +++ b/orchestrator/src/server/services/summary.ts @@ -2,6 +2,8 @@ * Service for generating tailored resume content (Summary, Headline, Skills). */ +import { getSetting } from '../repositories/settings.js'; + const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'; export interface TailoredData { @@ -30,7 +32,10 @@ export async function generateTailoring( return { success: false, error: 'API key not configured' }; } - const model = process.env.MODEL || 'openai/gpt-4o-mini'; + const overrideModel = await getSetting('model'); + const overrideModelTailoring = await getSetting('modelTailoring'); + // Precedence: Tailoring-specific override > Global override > Env var > Default + const model = overrideModelTailoring || overrideModel || process.env.MODEL || 'openai/gpt-4o-mini'; const prompt = buildTailoringPrompt(profile, jobDescription); try { diff --git a/orchestrator/src/shared/types.ts b/orchestrator/src/shared/types.ts index 43aaf92..4e8d5f7 100644 --- a/orchestrator/src/shared/types.ts +++ b/orchestrator/src/shared/types.ts @@ -211,6 +211,14 @@ export interface AppSettings { model: string; defaultModel: string; overrideModel: string | null; + // Specific model overrides + modelScorer: string; // resolved + overrideModelScorer: string | null; + modelTailoring: string; // resolved + overrideModelTailoring: string | null; + modelProjectSelection: string; // resolved + overrideModelProjectSelection: string | null; + pipelineWebhookUrl: string; defaultPipelineWebhookUrl: string; overridePipelineWebhookUrl: string | null;