
Company News
Andrew Becherer Joins Socket as Chief Information Security Officer
Socket’s first CISO brings deep experience securing high-growth SaaS companies as open source supply chain threats accelerate.
@odda-ai/matching-core
Advanced tools
Core AI provider library with support for OpenAI, Ollama, and Anthropic
Libreria TypeScript modulare per l'integrazione di provider AI multipli, parsing di CV in PDF e analisi intelligente di competenze e profili professionali.
@odda-ai/matching-core è una libreria core che fornisce:
npm install @odda-ai/matching-core
# Se usi OpenAI
npm install openai
# Se usi Anthropic Claude
npm install @anthropic-ai/sdk
# Se usi Ollama (nessuna dipendenza extra)
import { createAIProvider, AiTalentService } from '@odda-ai/matching-core';
// Crea provider AI
const aiProvider = createAIProvider({
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4-turbo-preview',
});
// Crea service principale
const aiTalent = new AiTalentService(aiProvider);
import fs from 'fs';
// Leggi CV PDF
const cvBuffer = fs.readFileSync('./cv-mario-rossi.pdf');
// Analizza CV
const analysis = await aiTalent.cv.analyzeCvResume(cvBuffer);
console.log('Nome:', analysis.personalInfo.firstName, analysis.personalInfo.lastName);
console.log('Seniority:', analysis.overallSeniority);
console.log('Skills:', analysis.technicalSkills.map(s => s.name));
console.log('Certificazioni:', analysis.certifications.length);
const jobDescription = `
Cerchiamo un Senior Full-Stack Developer con esperienza in:
- React, TypeScript, Node.js
- AWS, Docker, Kubernetes
- PostgreSQL, MongoDB
`;
const existingSkills = [
'React', 'Vue', 'Angular', 'TypeScript', 'JavaScript',
'Node.js', 'Python', 'AWS', 'Azure', 'Docker'
];
const jobSkills = await aiTalent.jobs.getSkillsFromJobDescription(
jobDescription,
existingSkills
);
console.log('Skills richieste:', jobSkills);
@odda-ai/matching-core/
├── ai/ # AI Provider Layer
│ ├── AIProvider.ts # Interface unificata
│ ├── factory.ts # Factory per creazione provider
│ ├── registry.ts # Registry adapter
│ ├── types.ts # Types condivisi
│ └── adapters/
│ ├── OpenAIAdapter.ts # Adapter OpenAI
│ ├── AnthropicAdapter.ts # Adapter Anthropic
│ └── OllamaAdapter.ts # Adapter Ollama
│
├── cv-parser/ # PDF Parsing Layer
│ ├── PDFParserService.ts # Service parsing PDF
│ └── types.ts # Types per parsing
│
└── features/ # Business Logic Layer
├── ai-talent.service.ts # Facade principale
├── ai-cv-resume.service.ts # CV analysis
├── ai-talent-review.service.ts # Talent review (free-form AI)
├── job-matcher.service.ts # Job skill extraction
├── cv-chunking.service.ts # CV chunking per RAG
├── prompts.ts # AI prompts
├── system-messages.ts # System messages
└── types.ts # Response types
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ PDF CV │──────▶│ PDFParser │──────▶│ Raw Text │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Structured │◀──────│ AI Provider │◀──────│ Prompts │
│ Output │ │ (GPT/Claude) │ │ + Context │
└─────────────┘ └──────────────┘ └─────────────┘
| Provider | Model Support | Streaming | Vision | Function Calling |
|---|---|---|---|---|
| OpenAI | GPT-3.5, GPT-4, GPT-4o | ✅ | ✅ | ✅ |
| Anthropic | Claude 3 (Opus, Sonnet, Haiku) | ✅ | ✅ | ✅ |
| Ollama | Llama 2, Mistral, CodeLlama, etc. | ✅ | ❌ | ⚠️ Limited |
import { createAIProvider } from '@odda-ai/matching-core';
// OpenAI
const openai = createAIProvider({
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4-turbo-preview',
});
// Anthropic Claude
const claude = createAIProvider({
provider: 'anthropic',
apiKey: process.env.ANTHROPIC_API_KEY,
model: 'claude-3-opus-20240229',
});
// Ollama (local)
const ollama = createAIProvider({
provider: 'ollama',
baseURL: 'http://localhost:11434',
model: 'llama2',
});
const provider = createAIProvider({
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4',
temperature: 0.3, // Più deterministico
maxTokens: 4000, // Max token response
responseFormat: 'json', // JSON mode
});
import { AIProvider } from '@odda-ai/matching-core';
// Usa direttamente il provider
const response = await aiProvider.chat([
{
role: 'system',
content: 'You are a helpful assistant.',
},
{
role: 'user',
content: 'Explain TypeScript generics.',
},
]);
console.log(response.content);
import { PDFParserService } from '@odda-ai/matching-core';
const parser = new PDFParserService();
// Parse PDF
const result = await parser.parsePDF(pdfBuffer);
console.log('Text:', result.text);
console.log('Pages:', result.numPages);
console.log('Info:', result.info);
const result = await parser.parsePDF(pdfBuffer, {
max: 50, // Max pages to parse
version: '1.10.100', // PDF.js version
verbosity: 0, // Logging level (0-5)
});
try {
const result = await parser.parsePDF(buffer);
} catch (error) {
if (error.message.includes('Invalid PDF')) {
console.error('File non è un PDF valido');
} else {
console.error('Errore parsing:', error.message);
}
}
Facade principale che orchestra tutti i servizi.
import { AiTalentService } from '@odda-ai/matching-core';
const service = new AiTalentService(aiProvider);
// CV Analysis (output strutturato)
const cvAnalysis = await service.cv.analyzeCvResume(buffer);
// CV Chunking (per RAG/Vector DB)
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis, {
chunkSize: 500,
overlap: 50,
});
// Job Skills Extraction
const skills = await service.jobs.getSkillsFromJobDescription(
jobDescription,
existingSkills
);
// Talent Review (analisi libera via AI, senza schema fisso)
const review = await service.talentReview.analyzeResume(buffer);
enum Seniority {
JUNIOR = 'JUNIOR',
MID = 'MID',
SENIOR = 'SENIOR',
LEAD = 'LEAD',
PRINCIPAL = 'PRINCIPAL',
}
type PersonalInfo = {
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
address?: string;
dateOfBirth?: string; // YYYY-MM-DD
nationality?: string;
linkedIn?: string;
github?: string;
website?: string;
};
type TechnicalSkill = {
name: string;
proficiency: number; // 0-100
isInferred: boolean; // true se dedotta da skill correlate
seniority: Seniority;
};
type Certification = {
name: string;
issuer?: string;
year?: number;
};
type CvAnalysisResponse = {
personalInfo: PersonalInfo;
description: string; // profilo professionale (2-3 frasi)
technicalSkills: TechnicalSkill[];
workExperienceSummary: string; // riassunto testuale delle esperienze
certifications: Certification[];
overallSeniority: Seniority;
yearsOfExperience: number;
};
Nota:
workExperienceSummaryè una stringa testuale sintetica, non un array di esperienze strutturate. Il prompt AI è progettato per la massima stabilità e ripetibilità dell'output.
Divide il CV analysis in chunk per vector databases o RAG systems.
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis, {
chunkSize: 500, // Caratteri per chunk
overlap: 50, // Overlap tra chunk
strategy: 'semantic' // 'semantic' | 'fixed'
});
// Ogni chunk contiene:
chunks.forEach(chunk => {
console.log('Text:', chunk.text);
console.log('Type:', chunk.type); // 'personal' | 'skills' | 'experience' | 'education'
console.log('Metadata:', chunk.metadata);
console.log('Embedding:', chunk.embedding); // Optional, se generato
});
const skills = await service.jobs.getSkillsFromJobDescription(
jobDescription,
existingSkillsList
);
La risposta è tipizzata tramite JobSkillType:
enum JobSkillType {
TECHNICAL_REQUIRED = 'technical_required',
TECHNICAL_NICE_TO_HAVE = 'technical_nice_to_have',
SOFT_REQUIRED = 'soft_required',
SOFT_NICE_TO_HAVE = 'soft_nice_to_have',
}
Analisi libera di un CV via AI senza schema rigido. Utile per note di screening, commenti HR, ecc.
const review = await service.talentReview.analyzeResume(buffer);
// Restituisce il JSON grezzo parsato dalla risposta AI (struttura flessibile)
function createAIProvider(config: AIProviderConfig): AIProvider
Parameters:
provider: 'openai' | 'anthropic' | 'ollama'apiKey: string (required per openai/anthropic)model: string (es. 'gpt-4', 'claude-3-opus-20240229')baseURL?: string (per Ollama o proxy)temperature?: number (0-2)maxTokens?: numberresponseFormat?: 'text' | 'json'new AiTalentService(aiProvider: AIProvider)
cv.analyzeCvResume()
async analyzeCvResume(
pdfBuffer: Buffer,
options?: AnalyzeResumeOptions
): Promise<CvAnalysisResponse>
cv.chunkCvAnalysis()
async chunkCvAnalysis(
cvAnalysis: CvAnalysisResponse,
options?: ChunkingOptions
): Promise<CvChunk[]>
jobs.getSkillsFromJobDescription()
async getSkillsFromJobDescription(
jobDescription: string,
existingSkills: string[]
): Promise<JobSkill[]>
talentReview.analyzeResume()
async analyzeResume(
pdfBuffer: Buffer,
options?: AnalyzeResumeOptions
): Promise<unknown> // struttura flessibile, parsata dalla risposta AI
class PDFParserService {
async parsePDF(
buffer: Buffer,
options?: ParseOptions
): Promise<ParsedPDF>
}
async function batchAnalyze(cvPaths: string[]) {
const results = [];
for (const path of cvPaths) {
try {
const buffer = fs.readFileSync(path);
const analysis = await aiTalent.cv.analyzeCvResume(buffer);
results.push({
path,
status: 'success',
data: analysis,
});
} catch (error) {
results.push({
path,
status: 'error',
error: error.message,
});
}
}
return results;
}
function fuzzyMatchSkills(
cvSkills: string[],
jobSkills: string[],
threshold: number = 0.8
) {
const matches = [];
for (const cvSkill of cvSkills) {
for (const jobSkill of jobSkills) {
const similarity = calculateSimilarity(cvSkill, jobSkill);
if (similarity >= threshold) {
matches.push({
cvSkill,
jobSkill,
similarity,
});
}
}
}
return matches;
}
import { Pinecone } from '@pinecone-database/pinecone';
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.index('cv-embeddings');
// Chunka e genera embeddings
const chunks = await service.cv.chunkCvAnalysis(cvAnalysis);
// Upsert in Pinecone
const vectors = chunks.map((chunk, i) => ({
id: `cv-${cvAnalysis.personalInfo.firstName}-${cvAnalysis.personalInfo.lastName}-${i}`,
values: chunk.embedding || [], // Generate with OpenAI embeddings API
metadata: {
text: chunk.text,
type: chunk.type,
...chunk.metadata,
},
}));
await index.upsert(vectors);
class AIService {
private providers: Map<string, AIProvider>;
constructor() {
this.providers = new Map([
['fast', createAIProvider({ provider: 'openai', model: 'gpt-3.5-turbo' })],
['accurate', createAIProvider({ provider: 'openai', model: 'gpt-4' })],
['local', createAIProvider({ provider: 'ollama', model: 'llama2' })],
]);
}
async analyze(cv: Buffer, mode: 'fast' | 'accurate' | 'local') {
const provider = this.providers.get(mode)!;
const service = new AiTalentService(provider);
return service.cv.analyzeCvResume(cv);
}
}
# OpenAI
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4-turbo-preview
# Anthropic
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-3-opus-20240229
# Ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama2
// config/ai.config.ts
import { AIProviderConfig } from '@odda-ai/matching-core';
export const aiConfig: AIProviderConfig = {
provider: process.env.AI_PROVIDER as any || 'openai',
apiKey: process.env.AI_API_KEY || '',
model: process.env.AI_MODEL || 'gpt-4',
temperature: 0.3,
maxTokens: 4000,
responseFormat: 'json',
};
Libreria fully-typed con completo supporto TypeScript.
import type {
// Provider types
AIProvider,
AIProviderConfig,
// CV Analysis types
CvAnalysisResponse,
PersonalInfo,
TechnicalSkill,
Certification,
// Enums
Seniority,
JobSkillType,
// Chunking types
CvChunk,
ChunkingOptions,
ChunkingResult,
// Misc
AnalyzeResumeOptions,
} from '@odda-ai/matching-core';
interface CustomAnalysis extends CvAnalysisResponse {
customField: string;
}
const analysis: CustomAnalysis = {
...await service.cv.analyzeCvResume(buffer),
customField: 'custom value',
};
import { describe, it, expect } from 'vitest';
import { createAIProvider } from '@odda-ai/matching-core';
describe('AI Provider', () => {
it('should create OpenAI provider', () => {
const provider = createAIProvider({
provider: 'openai',
apiKey: 'test-key',
});
expect(provider).toBeDefined();
});
});
describe('CV Analysis', () => {
it('should analyze CV correctly', async () => {
const buffer = fs.readFileSync('./test/fixtures/sample-cv.pdf');
const analysis = await service.cv.analyzeCvResume(buffer);
expect(analysis.personalInfo.firstName).toBeDefined();
expect(analysis.technicalSkills.length).toBeGreaterThan(0);
expect(analysis.overallSeniority).toMatch(/JUNIOR|MID|SENIOR|LEAD|PRINCIPAL/);
});
});
ISC
Contributions are welcome! Please open an issue or submit a pull request.
Made with ❤️ by Odda Studio
FAQs
Core AI provider library with support for OpenAI, Ollama, and Anthropic
We found that @odda-ai/matching-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket’s first CISO brings deep experience securing high-growth SaaS companies as open source supply chain threats accelerate.

Company News
Replit is integrating Socket Firewall into its AI-powered development experience to help protect builders from malicious open source packages.

Security News
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.