Skip to content

Commit b52ad3b

Browse files
authored
Merge pull request #1165 from narengogi/chore/openai-compliance-improvements
improve strict openai compliance
2 parents 56fc835 + 11012ed commit b52ad3b

File tree

7 files changed

+101
-12
lines changed

7 files changed

+101
-12
lines changed

src/providers/anthropic/chatComplete.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {
1414
import {
1515
generateErrorResponse,
1616
generateInvalidProviderResponseError,
17+
transformFinishReason,
1718
} from '../utils';
18-
import { AnthropicStreamState } from './types';
19+
import { ANTHROPIC_STOP_REASON, AnthropicStreamState } from './types';
1920

2021
// TODO: this configuration does not enforce the maximum token limit for the input parameter. If you want to enforce this, you might need to add a custom validation function or a max property to the ParameterConfig interface, and then use it in the input configuration. However, this might be complex because the token count is not a simple length check, but depends on the specific tokenization method used by the model.
2122

@@ -456,7 +457,7 @@ export interface AnthropicChatCompleteResponse {
456457
type: string;
457458
role: string;
458459
content: AnthropicContentItem[];
459-
stop_reason: string;
460+
stop_reason: ANTHROPIC_STOP_REASON;
460461
model: string;
461462
stop_sequence: null | string;
462463
usage: {
@@ -474,7 +475,7 @@ export interface AnthropicChatCompleteStreamResponse {
474475
type?: string;
475476
text?: string;
476477
partial_json?: string;
477-
stop_reason?: string;
478+
stop_reason?: ANTHROPIC_STOP_REASON;
478479
};
479480
content_block?: {
480481
type: string;
@@ -590,7 +591,10 @@ export const AnthropicChatCompleteResponseTransform: (
590591
},
591592
index: 0,
592593
logprobs: null,
593-
finish_reason: response.stop_reason,
594+
finish_reason: transformFinishReason(
595+
response.stop_reason,
596+
strictOpenAiCompliance
597+
),
594598
},
595599
],
596600
usage: {
@@ -703,6 +707,7 @@ export const AnthropicChatCompleteStreamChunkTransform: (
703707
);
704708
}
705709

710+
// final chunk
706711
if (parsedChunk.type === 'message_delta' && parsedChunk.usage) {
707712
const totalTokens =
708713
(streamState?.usage?.prompt_tokens ?? 0) +
@@ -720,7 +725,10 @@ export const AnthropicChatCompleteStreamChunkTransform: (
720725
{
721726
index: 0,
722727
delta: {},
723-
finish_reason: parsedChunk.delta?.stop_reason,
728+
finish_reason: transformFinishReason(
729+
parsedChunk.delta?.stop_reason,
730+
strictOpenAiCompliance
731+
),
724732
},
725733
],
726734
usage: {
@@ -791,7 +799,7 @@ export const AnthropicChatCompleteStreamChunkTransform: (
791799
},
792800
index: 0,
793801
logprobs: null,
794-
finish_reason: parsedChunk.delta?.stop_reason ?? null,
802+
finish_reason: null,
795803
},
796804
],
797805
})}` + '\n\n'

src/providers/anthropic/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ export type AnthropicStreamState = {
88
};
99
model?: string;
1010
};
11+
12+
// https://docs.anthropic.com/en/api/messages#response-stop-reason
13+
export enum ANTHROPIC_STOP_REASON {
14+
max_tokens = 'max_tokens',
15+
stop_sequence = 'stop_sequence',
16+
tool_use = 'tool_use',
17+
end_turn = 'end_turn',
18+
pause_turn = 'pause_turn',
19+
}

src/providers/bedrock/chatComplete.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import {
2020
import {
2121
generateErrorResponse,
2222
generateInvalidProviderResponseError,
23+
transformFinishReason,
2324
} from '../utils';
2425
import {
2526
BedrockAI21CompleteResponse,
2627
BedrockCohereCompleteResponse,
2728
BedrockCohereStreamChunk,
2829
} from './complete';
2930
import { BedrockErrorResponse } from './embed';
31+
import { BEDROCK_STOP_REASON } from './types';
3032
import {
3133
getBedrockErrorChunk,
3234
transformAdditionalModelRequestFields,
@@ -452,7 +454,7 @@ interface BedrockChatCompletionResponse {
452454
content: BedrockContentItem[];
453455
};
454456
};
455-
stopReason: string;
457+
stopReason: BEDROCK_STOP_REASON;
456458
usage: {
457459
inputTokens: number;
458460
outputTokens: number;
@@ -553,7 +555,10 @@ export const BedrockChatCompleteResponseTransform: (
553555
content_blocks: contentBlocks,
554556
}),
555557
},
556-
finish_reason: response.stopReason,
558+
finish_reason: transformFinishReason(
559+
response.stopReason,
560+
strictOpenAiCompliance
561+
),
557562
},
558563
],
559564
usage: {
@@ -608,7 +613,7 @@ export interface BedrockChatCompleteStreamChunk {
608613
input?: object;
609614
};
610615
};
611-
stopReason?: string;
616+
stopReason?: BEDROCK_STOP_REASON;
612617
metrics?: {
613618
latencyMs: number;
614619
};
@@ -624,7 +629,7 @@ export interface BedrockChatCompleteStreamChunk {
624629
}
625630

626631
interface BedrockStreamState {
627-
stopReason?: string;
632+
stopReason?: BEDROCK_STOP_REASON;
628633
currentToolCallIndex?: number;
629634
}
630635

@@ -653,6 +658,7 @@ export const BedrockChatCompleteStreamChunkTransform: (
653658
streamState.currentToolCallIndex = -1;
654659
}
655660

661+
// final chunk
656662
if (parsedChunk.usage) {
657663
const shouldSendCacheUsage =
658664
parsedChunk.usage.cacheWriteInputTokens ||
@@ -668,7 +674,10 @@ export const BedrockChatCompleteStreamChunkTransform: (
668674
{
669675
index: 0,
670676
delta: {},
671-
finish_reason: streamState.stopReason,
677+
finish_reason: transformFinishReason(
678+
streamState.stopReason,
679+
strictOpenAiCompliance
680+
),
672681
},
673682
],
674683
usage: {

src/providers/bedrock/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,12 @@ export interface BedrockInferenceProfile {
7878
status: string;
7979
type: string;
8080
}
81+
82+
export enum BEDROCK_STOP_REASON {
83+
end_turn = 'end_turn',
84+
tool_use = 'tool_use',
85+
max_tokens = 'max_tokens',
86+
stop_sequence = 'stop_sequence',
87+
guardrail_intervened = 'guardrail_intervened',
88+
content_filtered = 'content_filtered',
89+
}

src/providers/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Context } from 'hono';
22
import { Message, Options, Params } from '../types/requestBody';
3+
import { ANTHROPIC_STOP_REASON } from './anthropic/types';
4+
import { BEDROCK_STOP_REASON } from './bedrock/types';
35

46
/**
57
* Configuration for a parameter.
@@ -400,3 +402,15 @@ export interface StreamContentBlock {
400402
data?: string;
401403
};
402404
}
405+
406+
export enum FINISH_REASON {
407+
stop = 'stop',
408+
length = 'length',
409+
tool_calls = 'tool_calls',
410+
content_filter = 'content_filter',
411+
function_call = 'function_call',
412+
}
413+
414+
export type PROVIDER_FINISH_REASON =
415+
| ANTHROPIC_STOP_REASON
416+
| BEDROCK_STOP_REASON;

src/providers/utils.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { ErrorResponse } from './types';
1+
import { FINISH_REASON, ErrorResponse, PROVIDER_FINISH_REASON } from './types';
2+
import { finishReasonMap } from './utils/finishReasonMap';
23

34
export const generateInvalidProviderResponseError: (
45
response: Record<string, any>,
@@ -57,3 +58,23 @@ export function splitString(input: string, separator: string): SplitResult {
5758
after: input.substring(sepIndex + 1),
5859
};
5960
}
61+
62+
/*
63+
Transforms the finish reason from the provider to the finish reason used by the OpenAI API.
64+
If the finish reason is not found in the map, it will return the stop reason.
65+
If the strictOpenAiCompliance is true, it will return the finish reason from the map.
66+
If the strictOpenAiCompliance is false, it will return the finish reason from the provider.
67+
NOTE: this function always returns a finish reason
68+
*/
69+
export const transformFinishReason = (
70+
finishReason?: PROVIDER_FINISH_REASON,
71+
strictOpenAiCompliance?: boolean
72+
): FINISH_REASON | PROVIDER_FINISH_REASON => {
73+
if (!finishReason) return FINISH_REASON.stop;
74+
if (!strictOpenAiCompliance) return finishReason;
75+
const transformedFinishReason = finishReasonMap.get(finishReason);
76+
if (!transformedFinishReason) {
77+
return FINISH_REASON.stop;
78+
}
79+
return transformedFinishReason;
80+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ANTHROPIC_STOP_REASON } from '../anthropic/types';
2+
import { FINISH_REASON, PROVIDER_FINISH_REASON } from '../types';
3+
import { BEDROCK_STOP_REASON } from '../bedrock/types';
4+
5+
export const finishReasonMap = new Map<PROVIDER_FINISH_REASON, FINISH_REASON>([
6+
// https://docs.anthropic.com/en/api/messages#response-stop-reason
7+
[ANTHROPIC_STOP_REASON.stop_sequence, FINISH_REASON.stop],
8+
[ANTHROPIC_STOP_REASON.end_turn, FINISH_REASON.stop],
9+
[ANTHROPIC_STOP_REASON.pause_turn, FINISH_REASON.stop],
10+
[ANTHROPIC_STOP_REASON.tool_use, FINISH_REASON.tool_calls],
11+
[ANTHROPIC_STOP_REASON.max_tokens, FINISH_REASON.length],
12+
// https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_ResponseSyntax
13+
[BEDROCK_STOP_REASON.end_turn, FINISH_REASON.stop],
14+
[BEDROCK_STOP_REASON.tool_use, FINISH_REASON.tool_calls],
15+
[BEDROCK_STOP_REASON.max_tokens, FINISH_REASON.length],
16+
[BEDROCK_STOP_REASON.stop_sequence, FINISH_REASON.stop],
17+
[BEDROCK_STOP_REASON.guardrail_intervened, FINISH_REASON.content_filter],
18+
[BEDROCK_STOP_REASON.content_filtered, FINISH_REASON.content_filter],
19+
]);

0 commit comments

Comments
 (0)