Skip to content

Commit 7e557d4

Browse files
authored
Hotfix | New evaluations-api schema fix (#21)
* fix: new Evaluations-api schema fixes * Bump version * Bump version
1 parent 4dfc810 commit 7e557d4

7 files changed

Lines changed: 101 additions & 60 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "qualifire",
3-
"version": "0.1.0",
3+
"version": "1.3.0",
44
"description": "Qualifire client SDK",
55
"main": "./lib/index.js",
66
"files": [
@@ -123,4 +123,4 @@
123123
"zod": "^4.0.0"
124124
},
125125
"packageManager": "pnpm@10.14.0"
126-
}
126+
}

src/frameworks/claude/claude-converter.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,43 +119,43 @@ export class ClaudeCanonicalEvaluationStrategy
119119
for (const responseEvent of response) {
120120
switch (responseEvent.type) {
121121
case 'message_start':
122-
const rawMessageStartEvent = responseEvent as RawMessageStartEvent;
122+
const rawMessageStartEvent = responseEvent ;
123123
role = rawMessageStartEvent.message.role;
124124
accumulatedContent = [];
125125
accumulatedToolName = undefined;
126126
accumulatedToolId = undefined;
127127
accumulatedToolInput = [];
128128
break;
129129
case 'content_block_start':
130-
const rawContentBlockStartEvent = responseEvent as RawContentBlockStartEvent;
130+
const rawContentBlockStartEvent = responseEvent ;
131131
switch (rawContentBlockStartEvent.content_block.type) {
132132
case 'text':
133-
const textBlock = rawContentBlockStartEvent.content_block as TextBlock;
133+
const textBlock = rawContentBlockStartEvent.content_block ;
134134
accumulatedContent.push(textBlock.text)
135135
break;
136136
case 'tool_use':
137-
const toolUseBlock = rawContentBlockStartEvent.content_block as ToolUseBlock;
137+
const toolUseBlock = rawContentBlockStartEvent.content_block ;
138138
accumulatedToolId = toolUseBlock.id
139139
accumulatedToolName = toolUseBlock.name
140140
accumulatedToolInput = []
141141
break;
142142
case 'thinking':
143-
const thinkingBlock = rawContentBlockStartEvent.content_block as ThinkingBlock;
143+
const thinkingBlock = rawContentBlockStartEvent.content_block ;
144144
accumulatedContent.push(thinkingBlock.thinking)
145145
break;
146146
default:
147147
console.debug(`Invalid content block type: ${responseEvent}`);
148148
}
149149
break;
150150
case 'content_block_delta':
151-
const rawContentBlockDeltaEvent = responseEvent as RawContentBlockDeltaEvent;
151+
const rawContentBlockDeltaEvent = responseEvent ;
152152
switch (rawContentBlockDeltaEvent.delta.type) {
153153
case 'text_delta':
154-
const textDelta = rawContentBlockDeltaEvent.delta as TextDelta;
154+
const textDelta = rawContentBlockDeltaEvent.delta ;
155155
accumulatedContent.push(textDelta.text)
156156
break;
157157
case 'input_json_delta':
158-
const inputJsonDelta = rawContentBlockDeltaEvent.delta as InputJSONDelta;
158+
const inputJsonDelta = rawContentBlockDeltaEvent.delta ;
159159
accumulatedToolInput.push(inputJsonDelta.partial_json)
160160
break;
161161
default:
@@ -176,7 +176,7 @@ export class ClaudeCanonicalEvaluationStrategy
176176
};
177177
};
178178
if (!role) {
179-
console.debug(`role was not set`);
179+
console.debug('role was not set');
180180
continue;
181181
}
182182
messages.push({
@@ -246,7 +246,7 @@ export class ClaudeCanonicalEvaluationStrategy
246246
break;
247247
case 'tool_result':
248248
role = 'tool'; // Claude expects 'user' role for tool results. But Qualifire treats tool as results as it is sent from 'tool'
249-
const toolResultBlock = part as ToolResultBlockParam;
249+
const toolResultBlock = part ;
250250
if (typeof toolResultBlock.content === 'string') {
251251
aggregatedContent.push(toolResultBlock.content)
252252
} else {
@@ -272,7 +272,7 @@ export class ClaudeCanonicalEvaluationStrategy
272272

273273
// If we accumulated aggregatedContent or aggregatedToolCalls, add the message
274274
if (aggregatedContent.length > 0 || aggregatedToolCalls.length > 0) {
275-
let accumulatedMessage: LLMMessage = {
275+
const accumulatedMessage: LLMMessage = {
276276
role,
277277
};
278278

src/frameworks/gemini/gemini-converter.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class GeminiAICanonicalEvaluationStrategy
7272
}
7373
}
7474

75-
let contents: Array<Content> = [];
75+
const contents: Array<Content> = [];
7676
if (request?.config?.systemInstruction) {
7777
/*
7878
Gemini request.contents is an object called contentListUnions which can be
@@ -154,9 +154,9 @@ export class GeminiAICanonicalEvaluationStrategy
154154
): Promise<LLMMessage[]> {
155155
const messages: LLMMessage[] = [];
156156

157-
let accumulatedContentParts: string[] = [];
157+
const accumulatedContentParts: string[] = [];
158158
let currentRole = 'assistant';
159-
let toolCalls: LLMToolCall[] = [];
159+
const toolCalls: LLMToolCall[] = [];
160160
for (const chunk of response) {
161161
if (chunk?.candidates && chunk.candidates.length > 0) {
162162
if (chunk.candidates.length > 1) {
@@ -216,8 +216,8 @@ function convertContentToLLMMessage(content: any): LLMMessage | null {
216216

217217
// In Gemini role is optional, but by default the api is changing it to 'user' when no role is provided
218218
let role = content.role || 'user';
219-
let textContent: string[] = [];
220-
let tool_calls: LLMToolCall[] = [];
219+
const textContent: string[] = [];
220+
const tool_calls: LLMToolCall[] = [];
221221

222222
// Process all parts and aggregate them
223223
for (const part of content.parts) {
@@ -304,7 +304,7 @@ function convertContentListUnionsToContentList(
304304
if (inputs.every(isContent)) {
305305
convertedContents = inputs as Array<Content>;
306306
} else if (inputs.every(isPartOrString)) {
307-
let partInputs: Array<Part> = [];
307+
const partInputs: Array<Part> = [];
308308
for (const partOrString of inputs) {
309309
if (typeof partOrString === 'string') {
310310
partInputs.push({ text: partOrString });

src/frameworks/openai/openai-converter.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class OpenAICanonicalEvaluationStrategy
131131

132132
if (request?.messages) {
133133
messages.push(
134-
...this.convertRequestMessagesForChatCompletions(request.messages as ChatCompletionMessageParam[])
134+
...this.convertRequestMessagesForChatCompletions(request.messages )
135135
);
136136
}
137137

@@ -158,25 +158,25 @@ export class OpenAICanonicalEvaluationStrategy
158158
case 'system':
159159
case 'developer':
160160
case 'user':
161-
let textMessage = message as ChatCompletionDeveloperMessageParam // This works the same for System user and developer. Used this type just out of comfort
161+
const textMessage = message as ChatCompletionDeveloperMessageParam // This works the same for System user and developer. Used this type just out of comfort
162162
if (typeof textMessage.content === 'string') {
163-
content = textMessage.content as string;
163+
content = textMessage.content ;
164164
} else {
165-
content = (textMessage.content as ChatCompletionContentPartText[]).filter((part) => part.type === 'text').map((part) => part.text).join('')
165+
content = (textMessage.content ).filter((part) => part.type === 'text').map((part) => part.text).join('')
166166
}
167167
break;
168168
case 'assistant':
169-
let assistantMessage = message as ChatCompletionAssistantMessageParam
169+
const assistantMessage = message
170170
if (assistantMessage.content) {
171171
if (typeof assistantMessage.content === 'string') {
172172
content = assistantMessage.content;
173173
} else {
174-
content = (assistantMessage.content as Array<ChatCompletionContentPartText | ChatCompletionContentPartRefusal>).filter((part) => part.type === 'text').map((part) => (part as ChatCompletionContentPartText).text).join('')
174+
content = (assistantMessage.content ).filter((part) => part.type === 'text').map((part) => (part as ChatCompletionContentPartText).text).join('')
175175
}
176176
}
177177
if (assistantMessage.tool_calls) {
178178
toolCalls = assistantMessage.tool_calls
179-
.filter((toolCalls) => toolCalls.type === "function")
179+
.filter((toolCalls) => toolCalls.type === 'function')
180180
.map((toolCalls) => {
181181
// It's of type ChatCompletionMessageFunctionToolCall but it's not exported
182182
let toolArguments = {}
@@ -195,12 +195,12 @@ export class OpenAICanonicalEvaluationStrategy
195195
}
196196
break;
197197
case 'tool':
198-
let toolMessage = message as ChatCompletionToolMessageParam
198+
const toolMessage = message
199199
if (toolMessage.content) {
200200
if (typeof toolMessage.content === 'string') {
201201
content = toolMessage.content;
202202
} else {
203-
content = (toolMessage.content as Array<ChatCompletionContentPartText>).filter((part) => part.type === 'text').map((part) => part.text).join('')
203+
content = (toolMessage.content ).filter((part) => part.type === 'text').map((part) => part.text).join('')
204204
}
205205
}
206206
break;
@@ -520,7 +520,7 @@ export class OpenAICanonicalEvaluationStrategy
520520
private async handleChatCompletionsNonStreaming(
521521
response: ChatCompletion
522522
): Promise<LLMMessage[]> {
523-
response = response as ChatCompletion;
523+
response = response ;
524524

525525
const messages: LLMMessage[] = [];
526526

@@ -565,7 +565,7 @@ export class OpenAICanonicalEvaluationStrategy
565565

566566
if (response.output) {
567567
messages.push(
568-
...convertResponsesAPIMessagesToLLMMessages(response.output as Array<ResponseOutputItem>)
568+
...convertResponsesAPIMessagesToLLMMessages(response.output )
569569
);
570570
} else {
571571
console.debug(
@@ -619,8 +619,8 @@ function convertResponsesAPIMessagesToLLMMessages(
619619
continue
620620
}
621621

622-
let aggregatedContent: string[] = [];
623-
let aggregatedToolCalls: LLMToolCall[] = [];
622+
const aggregatedContent: string[] = [];
623+
const aggregatedToolCalls: LLMToolCall[] = [];
624624
let role = message.role;
625625

626626
for (const contentElement of message.content) {

src/frameworks/vercelai/vercelai-converter.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,14 @@ export class VercelAICanonicalEvaluationStrategy
210210
for (const message of messages) {
211211
switch (message.role) {
212212
case 'system':
213-
const systemMessage = message as SystemModelMessage;
213+
const systemMessage = message ;
214214
extractedMessages.push({
215215
role: 'system',
216216
content: systemMessage.content,
217217
});
218218
break;
219219
case 'user':
220-
const userMessage = message as UserModelMessage;
220+
const userMessage = message ;
221221
if (typeof userMessage.content === 'string') {
222222
extractedMessages.push({
223223
role: 'user',
@@ -237,7 +237,7 @@ export class VercelAICanonicalEvaluationStrategy
237237
}
238238
break;
239239
case 'assistant':
240-
const assistantMessage = message as AssistantModelMessage;
240+
const assistantMessage = message ;
241241
if (typeof assistantMessage.content === 'string') {
242242
extractedMessages.push({
243243
role: 'assistant',
@@ -264,7 +264,7 @@ export class VercelAICanonicalEvaluationStrategy
264264
}
265265
break;
266266
case 'tool':
267-
const toolMessage = message as ToolModelMessage;
267+
const toolMessage = message ;
268268
const toolContent = extractToolContent(toolMessage);
269269
extractedMessages.push({
270270
role: 'tool',
@@ -292,7 +292,7 @@ function extractToolContent(toolMessage: ToolModelMessage): string {
292292
}
293293
const firstContent = content[0];
294294
if (firstContent.type !== 'tool-result') {
295-
console.debug(`Expected tool-result in the first content of a tool message. Seems like invalid tool was given.`);
295+
console.debug('Expected tool-result in the first content of a tool message. Seems like invalid tool was given.');
296296
return '';
297297
}
298298
// LanguageModelV2ToolResultOutput is not exported unfortunaly so we have to use this type
@@ -316,12 +316,12 @@ function extractToolContent(toolMessage: ToolModelMessage): string {
316316
}
317317

318318
function extractToolCalls(content: ToolCallPart[]): LLMToolCall[] {
319-
let toolCalls: LLMToolCall[] = [];
319+
const toolCalls: LLMToolCall[] = [];
320320
for (const part of content) {
321321
if (part.type !== 'tool-call') {
322322
continue;
323323
}
324-
let toolCall: unknown = {
324+
const toolCall: unknown = {
325325
name: part.toolName,
326326
arguments: part.input,
327327
id: part.toolCallId,

src/index.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import * as traceloop from '@traceloop/node-server-sdk';
2+
import { CanonicalEvaluationStrategy } from './frameworks/canonical';
23
import { ClaudeCanonicalEvaluationStrategy } from './frameworks/claude/claude-converter';
34
import { GeminiAICanonicalEvaluationStrategy } from './frameworks/gemini/gemini-converter';
45
import { OpenAICanonicalEvaluationStrategy } from './frameworks/openai/openai-converter';
56
import { VercelAICanonicalEvaluationStrategy } from './frameworks/vercelai/vercelai-converter';
67
import { EvaluationProxyAPIRequest, EvaluationProxyAPIRequestSchema, EvaluationRequestV2Schema, type EvaluationRequestV2, type EvaluationResponse, type Framework } from './types';
7-
import { CanonicalEvaluationStrategy } from './frameworks/canonical';
88

99
export type {
10-
EvaluationRequestV2,
11-
EvaluationProxyAPIRequest,
12-
EvaluationResponse, Framework, LLMMessage
10+
EvaluationProxyAPIRequest, EvaluationRequestV2, EvaluationResponse, Framework, LLMMessage, ModelMode, PolicyTarget
1311
} from './types';
1412

1513

@@ -102,15 +100,12 @@ export class Qualifire {
102100
* framework: 'openai',
103101
* request: openaiRequest, // As given to openaiClient.chat.completions.create(), openaiClient.responses.create()
104102
* response: openaiResponse, // Response as returned by openaiClient.chat.completions.create() or openaiClient.responses.create()
105-
* dangerousContentCheck: true,
103+
* contentModerationCheck: true,
106104
* groundingCheck: true,
107105
* hallucinationsCheck: true,
108-
* harassmentCheck: true,
109-
* hateSpeechCheck: true,
110106
* instructionsFollowingCheck: true,
111107
* piiCheck: true,
112108
* promptInjections: true,
113-
* sexualContentCheck: true,
114109
* toolSelectionQualityCheck: false,
115110
* });
116111
*
@@ -158,7 +153,7 @@ export class Qualifire {
158153
* { role: 'user', content: 'What is the capital of France?' },
159154
* { role: 'assistant', content: 'Paris' }
160155
* ],
161-
* dangerousContentCheck: true,
156+
* contentModerationCheck: true,
162157
* hallucinationsCheck: true,
163158
* });
164159
* ```
@@ -202,21 +197,29 @@ export class Qualifire {
202197
* Evaluates using direct messages without conversion (overrides request/response if both are provided)
203198
*/
204199
private evaluateWithBackwardCompatibility = async (evaluationProxyAPIRequest: EvaluationProxyAPIRequest): Promise<EvaluationResponse | undefined> => {
200+
// Compute contentModerationCheck from deprecated fields or use the new field
201+
const contentModerationCheck = evaluationProxyAPIRequest.contentModerationCheck ||
202+
evaluationProxyAPIRequest.dangerous_content_check ||
203+
evaluationProxyAPIRequest.dangerousContentCheck ||
204+
evaluationProxyAPIRequest.harassment_check ||
205+
evaluationProxyAPIRequest.harassmentCheck ||
206+
evaluationProxyAPIRequest.hate_speech_check ||
207+
evaluationProxyAPIRequest.hateSpeechCheck ||
208+
evaluationProxyAPIRequest.sexual_content_check ||
209+
evaluationProxyAPIRequest.sexualContentCheck;
210+
205211
const url = `${this.baseUrl}/api/evaluation/evaluate`;
206212
const body = {
207213
input: evaluationProxyAPIRequest.input,
208214
output: evaluationProxyAPIRequest.output,
209215
messages: evaluationProxyAPIRequest.messages,
210216
available_tools: evaluationProxyAPIRequest.available_tools,
211-
dangerous_content_check: evaluationProxyAPIRequest.dangerous_content_check || evaluationProxyAPIRequest.dangerousContentCheck,
217+
content_moderation_check: contentModerationCheck,
212218
grounding_check: evaluationProxyAPIRequest.grounding_check || evaluationProxyAPIRequest.groundingCheck,
213219
hallucinations_check: evaluationProxyAPIRequest.hallucinations_check || evaluationProxyAPIRequest.hallucinationsCheck,
214-
harassment_check: evaluationProxyAPIRequest.harassment_check || evaluationProxyAPIRequest.harassmentCheck,
215-
hate_speech_check: evaluationProxyAPIRequest.hate_speech_check || evaluationProxyAPIRequest.hateSpeechCheck,
216220
instructions_following_check: evaluationProxyAPIRequest.instructions_following_check || evaluationProxyAPIRequest.instructionsFollowingCheck,
217221
pii_check: evaluationProxyAPIRequest.pii_check || evaluationProxyAPIRequest.piiCheck,
218222
prompt_injections: evaluationProxyAPIRequest.prompt_injections || evaluationProxyAPIRequest.promptInjections,
219-
sexual_content_check: evaluationProxyAPIRequest.sexual_content_check || evaluationProxyAPIRequest.sexualContentCheck,
220223
syntax_checks: evaluationProxyAPIRequest.syntax_checks || evaluationProxyAPIRequest.syntaxChecks,
221224
tool_selection_quality_check: evaluationProxyAPIRequest.tool_selection_quality_check || evaluationProxyAPIRequest.toolSelectionQualityCheck,
222225
assertions: evaluationProxyAPIRequest.assertions,
@@ -245,6 +248,13 @@ export class Qualifire {
245248
* Evaluates using framework converters for request/response
246249
*/
247250
private evaluateWithConverters = async (EvaluationRequestV2: EvaluationRequestV2): Promise<EvaluationResponse | undefined> => {
251+
// Compute contentModerationCheck from deprecated fields or use the new field
252+
const contentModerationCheck = EvaluationRequestV2.contentModerationCheck ||
253+
EvaluationRequestV2.dangerousContentCheck ||
254+
EvaluationRequestV2.harassmentCheck ||
255+
EvaluationRequestV2.hateSpeechCheck ||
256+
EvaluationRequestV2.sexualContentCheck;
257+
248258
const frameworkConverters: Record<Framework, () => CanonicalEvaluationStrategy<any, any>> = {
249259
'openai': () => new OpenAICanonicalEvaluationStrategy(),
250260
'vercelai': () => new VercelAICanonicalEvaluationStrategy(),
@@ -268,15 +278,12 @@ export class Qualifire {
268278
const body = {
269279
messages: evaluationRequest.messages,
270280
available_tools: evaluationRequest.available_tools,
271-
dangerous_content_check: EvaluationRequestV2.dangerousContentCheck,
281+
content_moderation_check: contentModerationCheck,
272282
grounding_check: EvaluationRequestV2.groundingCheck,
273283
hallucinations_check: EvaluationRequestV2.hallucinationsCheck,
274-
harassment_check: EvaluationRequestV2.harassmentCheck,
275-
hate_speech_check: EvaluationRequestV2.hateSpeechCheck,
276284
instructions_following_check: EvaluationRequestV2.instructionsFollowingCheck,
277285
pii_check: EvaluationRequestV2.piiCheck,
278286
prompt_injections: EvaluationRequestV2.promptInjections,
279-
sexual_content_check: EvaluationRequestV2.sexualContentCheck,
280287
syntax_checks: EvaluationRequestV2.syntaxChecks,
281288
tool_selection_quality_check: EvaluationRequestV2.toolSelectionQualityCheck,
282289
assertions: EvaluationRequestV2.assertions,

0 commit comments

Comments
 (0)