client.ts•4.24 kB
/**
* OpenAI API client for forensic relationship analysis
*/
import OpenAI from 'openai';
import { Connection } from './types.js';
export interface AnalysisResult {
relationship: string;
strength: number;
type: Connection['type'];
evidence: string;
}
/**
* OpenAI client for analyzing relationships between people in evidence
*/
export class OpenAIClient {
private openai: OpenAI;
private model: string;
constructor(apiKey: string, model: string = 'gpt-4o-mini', orgId?: string) {
this.openai = new OpenAI({
apiKey,
organization: orgId,
});
this.model = model;
}
/**
* Analyze relationship between two people using LLM
*/
async analyzeRelationship(
evidenceText: string,
person1: string,
person2: string
): Promise<AnalysisResult | null> {
try {
const prompt = this.buildAnalysisPrompt(evidenceText, person1, person2);
const response = await this.openai.chat.completions.create({
model: this.model,
messages: [
{
role: 'system',
content: 'You are a forensic analyst specializing in relationship mapping from evidence. Respond only with valid JSON or null.'
},
{
role: 'user',
content: prompt
}
],
temperature: 0.1,
max_tokens: 200
});
const content = response.choices[0]?.message?.content?.trim();
if (!content || content === 'null') {
return null;
}
const analysis = JSON.parse(content);
if (!this.isValidAnalysisResult(analysis)) {
return null;
}
return {
relationship: analysis.relationship,
evidence: analysis.evidence,
strength: Math.min(10, Math.max(1, analysis.strength)),
type: analysis.type
};
} catch (error) {
console.warn(`Error analyzing relationship between ${person1} and ${person2}:`, error);
return null;
}
}
/**
* Build the analysis prompt for the LLM
*/
private buildAnalysisPrompt(evidenceText: string, person1: string, person2: string): string {
return `Analyze the following evidence text and determine the relationship between "${person1}" and "${person2}".
Evidence:
${evidenceText}
Please analyze their relationship and respond with ONLY a JSON object (no additional text) containing:
{
"relationship": "brief description of their relationship",
"strength": number from 1-10 (1=weak mention, 10=direct strong connection),
"type": "communication|meeting|witness|location|organization|other",
"evidence": "brief description of the evidence supporting this relationship"
}
If no meaningful relationship exists, respond with: null
Guidelines for strength scoring:
- 9-10: Direct personal communication (emails, messages, calls)
- 7-8: Face-to-face meetings, shared events, strong professional collaboration
- 5-6: Group communications, shared concerns, indirect interactions
- 3-4: Mentioned together, weak professional associations
- 1-2: Coincidental mentions, very weak connections
Focus on:
- Direct interactions (emails, meetings, conversations)
- Shared concerns or collaborations
- Professional relationships
- Witness/observer relationships
- Geographic/location connections`;
}
/**
* Type guard to validate analysis result
*/
private isValidAnalysisResult(obj: any): obj is AnalysisResult {
return (
typeof obj === 'object' &&
typeof obj.relationship === 'string' &&
typeof obj.strength === 'number' &&
typeof obj.type === 'string' &&
typeof obj.evidence === 'string' &&
['communication', 'meeting', 'witness', 'location', 'organization', 'other'].includes(obj.type)
);
}
/**
* Test the OpenAI connection
*/
async testConnection(): Promise<boolean> {
try {
const response = await this.openai.chat.completions.create({
model: this.model,
messages: [{ role: 'user', content: 'Hello, respond with "OK"' }],
max_tokens: 5
});
return response.choices[0]?.message?.content?.trim() === 'OK';
} catch (error) {
console.error('OpenAI connection test failed:', error);
return false;
}
}
}