Skip to content
Merged
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: 21 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
AUTH0_DOMAIN="[AUTH0_DOMAIN]"
AUTH0_AUDIENCE="[AUTH0_AUDIENCE]"
AUTH0_M2M_DOMAIN="[AUTH0_M2M_DOMAIN]"
AUTH0_M2M_AUDIENCE="[AUTH0_M2M_AUDIENCE]"
MASTRA_DB_CONNECTION="[MASTRA_DB_CONNECTION]"
MASTRA_DB_SCHEMA="[MASTRA_DB_SCHEMA]"
TC_API_BASE="[TC_API_BASE]"
PORT="[PORT]"
OLLAMA_API_URL="[OLLAMA_API_URL]"
DISABLE_AUTH="[DISABLE_AUTH]"
SKILL_MATCHING_SEMANTIC_THRESHOLD="[SKILL_MATCHING_SEMANTIC_THRESHOLD]"
M2M_AUTH_CLIENT_ID="[M2M_AUTH_CLIENT_ID]"
M2M_AUTH_CLIENT_SECRET="[M2M_AUTH_CLIENT_SECRET]"
M2M_AUTH_PROXY_SERVER_URL="[M2M_AUTH_PROXY_SERVER_URL]"
M2M_AUTH_URL="[M2M_AUTH_URL]"
M2M_AUTH_DOMAIN="[M2M_AUTH_DOMAIN]"
M2M_AUTH_AUDIENCE="[M2M_AUTH_AUDIENCE]"
WORKSPACE_PATH="[ABS PATH_TO_WORKSPACE_FOLDER]"
WIPRO_API_KEY="[WIPRO_API_KEY]"
LOG_LEVEL="[LOG_LEVEL]"
MASTRA_STUDIO_PATH="[ABS PATH_TO_MASTRA_STUDIO_OUTPUT_FOLDER]"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ dist
*.db
*.db-*
.vscode
test-data
test-data
.env.sh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ tc-ai-api/
| `SKILL_MATCHING_FUZZY_MATCH_SIZE` | No | `3` | Number of candidates returned per fuzzy-match query |
| `SKILL_MATCHING_CONCURRENCY` | No | `5` | Concurrency limit for parallel skill-matching requests |
| `SKILL_MATCHING_SEMANTIC_THRESHOLD` | No | `0.45` | Max cosine distance for semantic matches (lower = stricter) |
| `SKILL_DISCOVERY_EVAL_SAMPLE_RATE` | No | `0.5` | Fraction of agent interactions sampled for evaluation scoring |
| `SKILL_DISCOVERY_EVAL_SAMPLE_RATE` | No | - | Fraction of agent interactions sampled for evaluation scoring |

> \* Auth0 variables are required unless `DISABLE_AUTH=true`.

Expand Down
36 changes: 19 additions & 17 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",
"build": "mastra build --studio",
"start": "mastra start",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand All @@ -25,28 +25,30 @@
"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.15.0",
"@mastra/evals": "^1.1.2",
"@mastra/libsql": "^1.7.1",
"@mastra/loggers": "^1.0.3",
"@mastra/memory": "^1.9.0",
"@mastra/observability": "^1.5.1",
"@mastra/pg": "^1.8.2",
"@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",
"@topcoder/wipro-ai-sdk-provider": "git+https://git.topcoder.com/Topcoder-Platform/Wipro-Provider-AI-SDK.git",
"ai": "^6.0.134",
"ai-sdk-ollama": "^3.8.1",
"ai": "^6.0.168",
"ai-sdk-ollama": "^3.8.3",
"tc-core-library-js": "^2.4.1",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^25.5.0",
"eslint": "^10.1.0",
"mastra": "^1.3.14",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.57.1",
"vitest": "^4.1.0"
"@types/node": "^25.6.0",
"eslint": "^10.2.1",
"mastra": "^1.6.0",
"prettier": "^3.8.3",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.0",
"vitest": "^4.1.4"
}
}
4,370 changes: 3,466 additions & 904 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

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

const MODEL_ID = 'us.anthropic.claude-sonnet-4-6';

/**
* Master agent responsible for parsing Topcoder challenge specifications.
Expand All @@ -14,12 +16,7 @@ import { wipro } from '../../../utils';
export const challengeParserAgent = new Agent({
id: 'challenge-parser-agent',
name: 'Challenge Specification Parser',
model: wipro.chatModel('gpt-5-chat', {
temperature: 0.1,
topK: 40,
topP: 0.9,
maxOutputTokens: 16384,
}),
model: bedrock(MODEL_ID),
instructions: {
role: 'system',
content: `You are an expert Topcoder challenge specification analyst.
Expand Down
11 changes: 4 additions & 7 deletions src/mastra/agents/jd/jd-rewriter-agent.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { Agent } from '@mastra/core/agent';
import { wipro } from '../../../utils';
import { bedrock } from '../../../utils';

const MODEL_ID = 'us.anthropic.claude-haiku-4-5-20251001-v1:0';

export const jdRewriterAgent = new Agent({
id: 'jd-rewriter-agent',
name: 'Job Description Rewriter',
model: wipro.chatModel('gpt-5-chat', {
temperature: 0.3,
topK: 40,
topP: 0.9,
maxOutputTokens: 16384,
}),
model: bedrock(MODEL_ID),
instructions: {
role: 'system',
content: `You are an expert technical recruiter and job description writer for Topcoder.
Expand Down
13 changes: 6 additions & 7 deletions src/mastra/agents/skills/skills-matching-agent.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { wipro } from '../../../utils';
import { bedrock } 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';

export const skillsMatchingAgent = new Agent({
id: 'skillsMatchingAgent',
name: 'Skill terms matching agent for Topcoder standardized skills from a given text',
Expand All @@ -26,23 +28,20 @@ Output requirements:
- No prose, no markdown, no extra keys.
`,
},
model: wipro.chatModel('gpt-5-chat', {
temperature: 0.1,
topP: 0.5,
}),
model: bedrock(MODEL_ID),
scorers: {
answerRelevancy: {
scorer: instanceScorers.instanceAnswerRelevancyScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE ?? 0.5),
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
},
},
promptAlignment: {
scorer: instanceScorers.instancePromptAlignmentScorer,
sampling: {
type: 'ratio',
rate: Number(process.env.EVAL_SAMPLE_RATE ?? 0.5),
rate: Number(process.env.EVAL_SAMPLE_RATE || 0),
},
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/mastra/scorers/instance-scorers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import {
createAnswerRelevancyScorer,
createPromptAlignmentScorerLLM,
} from '@mastra/evals/scorers/prebuilt';
import { ollama } from 'ai-sdk-ollama';
import { ollama } from '../../utils';

const evalModel = ollama(process.env.MASTRA_EVAL_MODEL ?? 'mistral:latest');
const ollamaBaseUrl = process.env.OLLAMA_API_URL;
const evalModelName = process.env.MASTRA_EVAL_MODEL ?? 'mistral:latest';

console.log(`[instance-scorers] Initializing eval model: ${evalModelName} at ${ollamaBaseUrl}`);

const evalModel = ollama(evalModelName);

export const instanceAnswerRelevancyScorer = createAnswerRelevancyScorer({
model: evalModel,
Expand Down
44 changes: 40 additions & 4 deletions src/mastra/workflows/jd/jd-autowrite-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,49 @@ import { generateWithStructuredOutputFallback } from '../../../utils/structured-
// Zod Schemas
// ---------------------------------------------------------------------------

function normalizeListField(value: unknown): unknown {
if (Array.isArray(value)) {
return value
.map((item) => String(item).trim())
.filter(Boolean);
}

if (typeof value !== 'string') {
return value;
}

const trimmed = value.trim();
if (!trimmed) {
return [];
}

const byLine = trimmed
.split(/\r?\n/)
.map((line) => line.replace(/^\s*(?:[-*•]|\d+[.)])\s+/, '').trim())
.filter(Boolean);

if (byLine.length > 1) {
return byLine;
}

return trimmed
.split(/\s*[;,]\s*/)
.map((entry) => entry.trim())
.filter(Boolean);
}

const normalizedStringArrayField = z.preprocess(
normalizeListField,
z.array(z.string().min(1)),
);

const rewrittenJdSchema = z.object({
title: z.string().describe('Concise, descriptive job title'),
overview: z.string().describe('2-4 sentence summary of the opportunity'),
responsibilities: z.array(z.string()).describe('Action-oriented bullet list of tasks and deliverables'),
requirements: z.array(z.string()).describe('Mandatory qualifications, skills, and experience'),
niceToHaves: z.array(z.string()).describe('Preferred but non-mandatory qualifications'),
skills: z.array(z.string()).describe('Key technical and soft skill keywords'),
responsibilities: normalizedStringArrayField.describe('Action-oriented bullet list of tasks and deliverables'),
requirements: normalizedStringArrayField.describe('Mandatory qualifications, skills, and experience'),
niceToHaves: normalizedStringArrayField.describe('Preferred but non-mandatory qualifications'),
skills: normalizedStringArrayField.describe('Key technical and soft skill keywords'),
formattedDescription: z.string().describe('Full markdown-formatted job description ready to post'),
});

Expand Down
2 changes: 1 addition & 1 deletion src/mastra/workflows/skills/skill-extraction-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const fuzzyMatchTermSkills = createStep({
matches: toolResult.matches,
};
} catch (error) {
logger?.warn('Skill fuzzymatch failed for term {term}', {
logger?.error('Skill fuzzymatch failed for term {term}', {
term: searchTerm,
error,
});
Expand Down
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './auth';
export * from './middleware';
export * from './logger';
export * from './auth/m2m.service'
export * from './providers/wipro';
export * from './providers/wipro';
export * from './providers/bedrock';
9 changes: 9 additions & 0 deletions src/utils/providers/bedrock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';

const bedrockProvider = createAmazonBedrock({
region: process.env.AWS_REGION || 'us-east-1',
credentialProvider: fromNodeProviderChain(),
});

export const bedrock = bedrockProvider;
5 changes: 5 additions & 0 deletions src/utils/structured-output-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ export function isStructuredOutputCompatibilityError(error: unknown): boolean {
if (!(error instanceof Error)) return false;
const message = error.message.toLowerCase();

// Schema-shape failures are content issues, not provider compatibility issues.
if (message.includes('validation failed') || message.includes('invalid input: expected')) {
return false;
}

return (
message.includes('response_format')
|| message.includes('structured output')
Expand Down