Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions .github/workflows/code_reviewer.yml

This file was deleted.

12 changes: 10 additions & 2 deletions appStartUp.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
#!/bin/bash
set -eo pipefail

# Start the app
pnpm start
# Force temp files to /tmp (writable even on read-only root filesystem)
export TMPDIR=/tmp
export TEMP=/tmp
export TMP=/tmp

# Disable Mastra Studio (it tries to bundle/write files at runtime)
export MASTRA_STUDIO_DISABLED=true

# Start the app - use pre-built output directly to avoid runtime bundling
node /app/.mastra/output/index.mjs
41 changes: 21 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"test": "vitest run",
"test:integration": "vitest run --config vitest.integration.config.ts",
"dev": "mastra dev",
"build": "mastra build --studio",
"build": "mastra build",
"start": "mastra start",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand All @@ -25,30 +25,31 @@
"node": ">=22.13.0"
},
"dependencies": {
"@ai-sdk/amazon-bedrock": "^4.0.96",
"@aws-sdk/credential-providers": "^3.1033.0",
"@mastra/auth-auth0": "^1.0.1",
"@mastra/core": "^1.25.0",
"@mastra/evals": "^1.2.1",
"@mastra/libsql": "^1.8.1",
"@mastra/loggers": "^1.1.1",
"@mastra/memory": "^1.15.1",
"@mastra/observability": "^1.9.1",
"@mastra/pg": "^1.9.1",
"@ai-sdk/amazon-bedrock": "^4.0.121",
"@ai-sdk/openai": "^3.0.74",
"@aws-sdk/credential-providers": "^3.1075.0",
"@mastra/auth-auth0": "^1.2.0",
"@mastra/core": "^1.46.0",
"@mastra/evals": "^1.4.0",
"@mastra/libsql": "^1.14.1",
"@mastra/loggers": "^1.2.0",
"@mastra/memory": "^1.21.1",
"@mastra/observability": "^1.15.1",
"@mastra/pg": "^1.14.1",
"@topcoder/wipro-ai-sdk-provider": "git+https://git.topcoder.com/Topcoder-Platform/Wipro-Provider-AI-SDK.git",
"ai": "^6.0.168",
"ai-sdk-ollama": "^3.8.3",
"ai": "^6.0.209",
"ai-sdk-ollama": "^3.8.8",
"tc-core-library-js": "^2.4.1",
"zod": "^4.3.6"
"zod": "^4.4.3"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^25.6.0",
"eslint": "^10.2.1",
"mastra": "^1.6.0",
"prettier": "^3.8.3",
"@types/node": "^26.0.1",
"eslint": "^10.5.0",
"mastra": "^1.15.1",
"prettier": "^3.8.4",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.0",
"vitest": "^4.1.4"
"typescript-eslint": "^8.62.0",
"vitest": "^4.1.9"
}
}
4,082 changes: 1,491 additions & 2,591 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/mastra/agents/challenge/challenge-parser-agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Agent } from '@mastra/core/agent';
import { bedrock } from '../../../utils';
import { createModel } from '../../../utils';

const MODEL_ID = 'us.anthropic.claude-sonnet-4-6';
const PROVIDER_NAME = process.env.CHALLENGE_PARSER_AI_PROVIDER || 'TC-Ollama';
const MODEL_ID = process.env.CHALLENGE_PARSER_AI_MODEL_ID || 'qwen3.5:4b';
const AGENT_ID = 'challenge-parser-agent';

/**
* Master agent responsible for parsing Topcoder challenge specifications.
Expand All @@ -14,9 +16,9 @@ const MODEL_ID = 'us.anthropic.claude-sonnet-4-6';
* codebase-detection, and submission-guidelines-extraction.
*/
export const challengeParserAgent = new Agent({
id: 'challenge-parser-agent',
id: AGENT_ID,
name: 'Challenge Specification Parser',
model: bedrock(MODEL_ID),
model: createModel(PROVIDER_NAME, MODEL_ID, AGENT_ID),
instructions: {
role: 'system',
content: `You are an expert Topcoder challenge specification analyst.
Expand Down Expand Up @@ -66,7 +68,6 @@ STRICT OUTPUT CONTRACT
────────────────────────────────────────────────────────
Return ONLY the JSON object matching the provided schema.
Do NOT add commentary, markdown fences, or extra keys.
Every field is mandatory per the schema — never omit a key.
/no_think`,
Every field is mandatory per the schema — never omit a key.`,
},
});
23 changes: 12 additions & 11 deletions src/mastra/agents/jd/jd-rewriter-agent.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Agent } from '@mastra/core/agent';
import { bedrock } from '../../../utils';
import { createModel } from '../../../utils';

const MODEL_ID = 'us.anthropic.claude-haiku-4-5-20251001-v1:0';
const PROVIDER_NAME = process.env.JD_REWRITER_AI_PROVIDER || 'TC-Ollama';
const MODEL_ID = process.env.JD_REWRITER_AI_MODEL_ID || 'qwen3.5:4b';
const AGENT_ID = 'jd-rewriter-agent';

export const jdRewriterAgent = new Agent({
id: 'jd-rewriter-agent',
name: 'Job Description Rewriter',
model: bedrock(MODEL_ID),
instructions: {
role: 'system',
content: `You are an expert technical recruiter and job description writer for Topcoder.
id: AGENT_ID,
name: 'Job Description Rewriter',
model: createModel(PROVIDER_NAME, MODEL_ID, AGENT_ID),
instructions: {
role: 'system',
content: `You are an expert technical recruiter and job description writer for Topcoder.
Your sole job is to take a raw, rough, or vague job description and rewrite it
into a clear, professional, well-structured format suitable for posting as a
Topcoder opportunity.
Expand Down Expand Up @@ -44,7 +46,6 @@ STRICT OUTPUT CONTRACT
────────────────────────────────────────────────────────
Return ONLY the JSON object matching the provided schema.
Do NOT add commentary, markdown fences, or extra keys.
Every field is mandatory per the schema — never omit a key.
/no_think`,
},
Every field is mandatory per the schema — never omit a key.`,
},
});
42 changes: 24 additions & 18 deletions src/mastra/agents/skills/skills-matching-agent.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { bedrock } from '../../../utils';
import { createModel } from '../../../utils';
import { PostgresStore } from '@mastra/pg';
import { instanceScorers } from '../../scorers/instance-scorers';

const MODEL_ID = 'us.anthropic.claude-haiku-4-5-20251001-v1:0';
// Feature flag: Enable scorers only in local dev
const IS_LOCAL_DEV = process.env.LOCAL_DEV === 'true';

const PROVIDER_NAME = process.env.SKILLS_EXTRACTOR_AI_PROVIDER || 'TC-Ollama';
const MODEL_ID = process.env.SKILLS_EXTRACTOR_AI_MODEL_ID || 'qwen3.5:4b';
const AGENT_ID = 'skillsMatchingAgent';

export const skillsMatchingAgent = new Agent({
id: 'skillsMatchingAgent',
id: AGENT_ID,
name: 'Skill terms matching agent for Topcoder standardized skills from a given text',
instructions: {
role: 'system',
Expand All @@ -28,23 +33,24 @@ Output requirements:
- No prose, no markdown, no extra keys.
`,
},
model: bedrock(MODEL_ID),
scorers: {
answerRelevancy: {
scorer: instanceScorers.instanceAnswerRelevancyScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
model: createModel(PROVIDER_NAME, MODEL_ID, AGENT_ID),
scorers: IS_LOCAL_DEV
? {
answerRelevancy: {
scorer: instanceScorers.instanceAnswerRelevancyScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
},
},
},
promptAlignment: {
scorer: instanceScorers.instancePromptAlignmentScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
promptAlignment: {
scorer: instanceScorers.instancePromptAlignmentScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
},
},
},
},
} : undefined,
memory: new Memory({
storage: new PostgresStore({
id: 'skills-matching-agent-memory',
Expand Down
34 changes: 28 additions & 6 deletions src/mastra/workflows/challenge/challenge-context-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,14 +946,36 @@ async function extractWithAI(
const agent = mastra.getAgentById('challenge-parser-agent');
const challengeText = buildChallengeText(data);

tcAILogger.info('[challenge-context:extractWithAI] Starting decomposed extraction (4 focused calls)...');
tcAILogger.info('[challenge-context:extractWithAI] Starting decomposed extraction...');

const [reqResult, techResult, codebaseResult, guidelinesResult] = await Promise.all([
extractRequirementsAndGroups(agent, challengeText),
extractTechAndRuntime(agent, challengeText),
extractExistingCodebase(agent, challengeText),
extractSubmissionGuidelines(agent, challengeText),
// Run requirements extraction first (sequential) to avoid orphaned promises on early failure
tcAILogger.debug('[challenge-context:extractWithAI] Step 1/4: extractRequirementsAndGroups');
let reqResult;
try {
reqResult = await extractRequirementsAndGroups(agent, challengeText);
tcAILogger.debug('[challenge-context:extractWithAI] Step 1/4: extractRequirementsAndGroups completed');
} catch (err) {
tcAILogger.error(`[challenge-context:extractWithAI] Step 1/4: extractRequirementsAndGroups FAILED: ${err}`);
throw err;
}

// Run remaining extractions in parallel with individual error tracking
tcAILogger.debug('[challenge-context:extractWithAI] Steps 2-4: Starting parallel extractions (tech, codebase, guidelines)');
const [techResult, codebaseResult, guidelinesResult] = await Promise.all([
extractTechAndRuntime(agent, challengeText).catch(err => {
tcAILogger.error(`[challenge-context:extractWithAI] Step 2/4: extractTechAndRuntime FAILED: ${err}`);
throw err;
}),
extractExistingCodebase(agent, challengeText).catch(err => {
tcAILogger.error(`[challenge-context:extractWithAI] Step 3/4: extractExistingCodebase FAILED: ${err}`);
throw err;
}),
extractSubmissionGuidelines(agent, challengeText).catch(err => {
tcAILogger.error(`[challenge-context:extractWithAI] Step 4/4: extractSubmissionGuidelines FAILED: ${err}`);
throw err;
}),
]);
tcAILogger.debug('[challenge-context:extractWithAI] Steps 2-4: All parallel extractions completed');

// -- Post-extraction validations ------------------------------------------
const descriptionText = (data.description as string) ?? '';
Expand Down
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export * from './middleware';
export * from './logger';
export * from './auth/m2m.service'
export * from './providers/wipro';
export * from './providers/bedrock';
export * from './providers/bedrock';
export * from './providers/model-factory';
35 changes: 35 additions & 0 deletions src/utils/providers/model-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ollama } from './ollama';
import { wipro } from './wipro';
import { bedrock } from './bedrock';
import { tcAILogger } from '../logger';
import { openai } from './openai';

export type SupportedProvider = 'TC-Ollama' | 'WiproAI' | 'AWSBedrock' | 'OpenAI';

export function createModel(providerName: string, modelName: string, agentId?: string) {
tcAILogger.info(`[Model Factory] PROVIDER: ${providerName}, MODEL: ${modelName} for AGENT: ${agentId ?? 'N/A'}`);

switch (providerName) {
case 'TC-Ollama':
return ollama(modelName, {
// options: {
// temperature: Number(process.env.OLLAMA_TEMPERATURE || 0.1),
// num_batch: Number(process.env.OLLAMA_NUM_BATCH || 1024),
// num_predict: Number(process.env.OLLAMA_NUM_PREDICT || 2048),
// }
});

case 'WiproAI':
return wipro.chatModel(modelName);

case 'AWSBedrock':
return bedrock(modelName);

case 'OpenAI':
return openai(modelName);

default:
tcAILogger.error(`[Model Factory] Unsupported LLM provider: ${providerName}. Supported providers: TC-Ollama, WiproAI, AWSBedrock, OpenAI`);
throw new Error(`Unsupported LLM provider: ${providerName}. Supported providers: TC-Ollama, WiproAI, AWSBedrock, OpenAI`);
}
}
5 changes: 5 additions & 0 deletions src/utils/providers/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createOpenAI } from '@ai-sdk/openai';

export const openai = createOpenAI({
// custom settings can be added here if needed
});