Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NovitaAI Provider #303

Merged
merged 1 commit into from
May 1, 2024
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
51 changes: 26 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div align="center">
****<div align="center">

<p align="right">
<strong>English</strong> | <a href="./README.cn.md">中文</a>
Expand Down Expand Up @@ -145,22 +145,23 @@ For other providers, change the `provider` & `model` to their respective values.

Head over to [Portkey docs](https://portkey.ai/docs/product/ai-gateway-streamline-llm-integrations) for detailed [guides & cookbooks](https://portkey.ai/docs/welcome/integration-guides) on more [provider integrations](https://portkey.ai/docs/welcome/integration-guides).

|| Provider | Support | Stream |
|---|---|---|---|
| <img src="docs/images/openai.png" width=35 />| [OpenAI](https://portkey.ai/docs/welcome/integration-guides/openai) | ✅ |✅ |
| <img src="docs/images/azure.png" width=35>| [Azure OpenAI](https://portkey.ai/docs/welcome/integration-guides/azure-openai) | ✅ |✅ |
| <img src="docs/images/anyscale.png" width=35>| [Anyscale](https://portkey.ai/docs/welcome/integration-guides/anyscale-llama2-mistral-zephyr) | ✅ | ✅ |
| <img src="https://upload.wikimedia.org/wikipedia/commons/2/2d/Google-favicon-2015.png" width=35>| [Google Gemini & Palm](https://portkey.ai/docs/welcome/integration-guides/gemini) | ✅ |✅ |
| <img src="docs/images/anthropic.png" width=35>| [Anthropic](https://portkey.ai/docs/welcome/integration-guides/anthropic) | ✅ |✅ |
| <img src="docs/images/cohere.png" width=35>| [Cohere](https://portkey.ai/docs/welcome/integration-guides/cohere) | ✅ |✅ |
| <img src="https://assets-global.website-files.com/64f6f2c0e3f4c5a91c1e823a/654693d569494912cfc0c0d4_favicon.svg" width=35>| [Together AI](https://portkey.ai/docs/welcome/integration-guides/together-ai) | ✅ |✅ |
| <img src="https://www.perplexity.ai/favicon.svg" width=35>| [Perplexity](https://portkey.ai/docs/welcome/integration-guides/perplexity-ai) | ✅ |✅ |
| <img src="https://docs.mistral.ai/img/favicon.ico" width=35>| [Mistral](https://portkey.ai/docs/welcome/integration-guides/mistral-ai) | ✅ |✅ |
| <img src="https://docs.nomic.ai/img/nomic-logo.png" width=35>| [Nomic](https://portkey.ai/docs/welcome/integration-guides/nomic) | ✅ |✅ |
| <img src="https://files.readme.io/d38a23e-small-studio-favicon.png" width=35>| [AI21](https://portkey.ai/docs/welcome/integration-guides) | ✅ |✅ |
| <img src="https://platform.stability.ai/small-logo-purple.svg" width=35>| [Stability AI](https://portkey.ai/docs/welcome/integration-guides/stability-ai) | ✅ |✅ |
| <img src="https://deepinfra.com/_next/static/media/logo.4a03fd3d.svg" width=35>| [DeepInfra](https://portkey.ai/docs/welcome/integration-guides) | ✅ |✅ |
| <img src="https://ollama.com/public/ollama.png" width=35>| [Ollama](https://portkey.ai/docs/welcome/integration-guides/ollama) | ✅ |✅ |
| | Provider | Support | Stream |
| -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------- | ------ |
| <img src="docs/images/openai.png" width=35 /> | [OpenAI](https://portkey.ai/docs/welcome/integration-guides/openai) | ✅ | ✅ |
| <img src="docs/images/azure.png" width=35> | [Azure OpenAI](https://portkey.ai/docs/welcome/integration-guides/azure-openai) | ✅ | ✅ |
| <img src="docs/images/anyscale.png" width=35> | [Anyscale](https://portkey.ai/docs/welcome/integration-guides/anyscale-llama2-mistral-zephyr) | ✅ | ✅ |
| <img src="https://upload.wikimedia.org/wikipedia/commons/2/2d/Google-favicon-2015.png" width=35> | [Google Gemini & Palm](https://portkey.ai/docs/welcome/integration-guides/gemini) | ✅ | ✅ |
| <img src="docs/images/anthropic.png" width=35> | [Anthropic](https://portkey.ai/docs/welcome/integration-guides/anthropic) | ✅ | ✅ |
| <img src="docs/images/cohere.png" width=35> | [Cohere](https://portkey.ai/docs/welcome/integration-guides/cohere) | ✅ | ✅ |
| <img src="https://assets-global.website-files.com/64f6f2c0e3f4c5a91c1e823a/654693d569494912cfc0c0d4_favicon.svg" width=35> | [Together AI](https://portkey.ai/docs/welcome/integration-guides/together-ai) | ✅ | ✅ |
| <img src="https://www.perplexity.ai/favicon.svg" width=35> | [Perplexity](https://portkey.ai/docs/welcome/integration-guides/perplexity-ai) | ✅ | ✅ |
| <img src="https://docs.mistral.ai/img/favicon.ico" width=35> | [Mistral](https://portkey.ai/docs/welcome/integration-guides/mistral-ai) | ✅ | ✅ |
| <img src="https://docs.nomic.ai/img/nomic-logo.png" width=35> | [Nomic](https://portkey.ai/docs/welcome/integration-guides/nomic) | ✅ | ✅ |
| <img src="https://files.readme.io/d38a23e-small-studio-favicon.png" width=35> | [AI21](https://portkey.ai/docs/welcome/integration-guides) | ✅ | ✅ |
| <img src="https://platform.stability.ai/small-logo-purple.svg" width=35> | [Stability AI](https://portkey.ai/docs/welcome/integration-guides/stability-ai) | ✅ | ✅ |
| <img src="https://deepinfra.com/_next/static/media/logo.4a03fd3d.svg" width=35> | [DeepInfra](https://portkey.ai/docs/welcome/integration-guides) | ✅ | ✅ |
| <img src="https://ollama.com/public/ollama.png" width=35> | [Ollama](https://portkey.ai/docs/welcome/integration-guides/ollama) | ✅ | ✅ |
| <img src="https://novita.ai/favicon.ico" width=35> | Novita AI | ✅ | ✅ | `/chat/completions`, `/completions` |

> [View the complete list of 100+ supported models here](https://portkey.ai/docs/welcome/what-is-portkey#ai-providers-supported)
<br>
Expand Down Expand Up @@ -302,14 +303,14 @@ Here's a guide to [use the config object in your request](https://portkey.ai/doc

## Supported SDKs

| Language | Supported SDKs |
|---|---|
| Node.js / JS / TS | [Portkey SDK](https://www.npmjs.com/package/portkey-ai) <br> [OpenAI SDK](https://www.npmjs.com/package/openai) <br> [LangchainJS](https://www.npmjs.com/package/langchain) <br> [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex) |
| Python | [Portkey SDK](https://pypi.org/project/portkey-ai/) <br> [OpenAI SDK](https://portkey.ai/docs/welcome/integration-guides/openai) <br> [Langchain](https://portkey.ai/docs/welcome/integration-guides/langchain-python) <br> [LlamaIndex](https://portkey.ai/docs/welcome/integration-guides/llama-index-python) |
| Go | [go-openai](https://github.com/sashabaranov/go-openai) |
| Java | [openai-java](https://github.com/TheoKanning/openai-java) |
| Rust | [async-openai](https://docs.rs/async-openai/latest/async_openai/) |
| Ruby | [ruby-openai](https://github.com/alexrudall/ruby-openai) |
| Language | Supported SDKs |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Node.js / JS / TS | [Portkey SDK](https://www.npmjs.com/package/portkey-ai) <br> [OpenAI SDK](https://www.npmjs.com/package/openai) <br> [LangchainJS](https://www.npmjs.com/package/langchain) <br> [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex) |
| Python | [Portkey SDK](https://pypi.org/project/portkey-ai/) <br> [OpenAI SDK](https://portkey.ai/docs/welcome/integration-guides/openai) <br> [Langchain](https://portkey.ai/docs/welcome/integration-guides/langchain-python) <br> [LlamaIndex](https://portkey.ai/docs/welcome/integration-guides/llama-index-python) |
| Go | [go-openai](https://github.com/sashabaranov/go-openai) |
| Java | [openai-java](https://github.com/TheoKanning/openai-java) |
| Rust | [async-openai](https://docs.rs/async-openai/latest/async_openai/) |
| Ruby | [ruby-openai](https://github.com/alexrudall/ruby-openai) |
<br>


Expand Down
2 changes: 2 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const MOONSHOT: string = 'moonshot';
export const OPENROUTER: string = 'openrouter';
export const LINGYI: string = 'lingyi';
export const ZHIPU: string = 'zhipu';
export const NOVITA_AI: string = 'novita-ai';

export const VALID_PROVIDERS = [
ANTHROPIC,
Expand Down Expand Up @@ -76,6 +77,7 @@ export const VALID_PROVIDERS = [
OPENROUTER,
LINGYI,
ZHIPU,
NOVITA_AI,
];

export const CONTENT_TYPES = {
Expand Down
2 changes: 2 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import MoonshotConfig from './moonshot';
import OpenrouterConfig from './openrouter';
import LingYiConfig from './lingyi';
import ZhipuConfig from './zhipu';
import NovitaAIConfig from './novita-ai';

const Providers: { [key: string]: ProviderConfigs } = {
openai: OpenAIConfig,
Expand Down Expand Up @@ -53,6 +54,7 @@ const Providers: { [key: string]: ProviderConfigs } = {
openrouter: OpenrouterConfig,
lingyi: LingYiConfig,
zhipu: ZhipuConfig,
'novita-ai': NovitaAIConfig,
};

export default Providers;
20 changes: 20 additions & 0 deletions src/providers/novita-ai/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ProviderAPIConfig } from '../types';

const NovitaAIApiConfig: ProviderAPIConfig = {
getBaseURL: () => 'https://api.novita.ai/v3/openai',
headers: ({ providerOptions }) => {
return { Authorization: `Bearer ${providerOptions.apiKey}` };
},
getEndpoint: ({ fn }) => {
switch (fn) {
case 'complete':
return '/v1/completions';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end point seems to be incorrect.
please refer to https://novita.ai/get-started/llm.html#example-with-curl-client

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi it's correct, we use the /v3/openai prefix, so the complete URL will be /v3/openai/v1/completion

Copy link
Collaborator

@sk-portkey sk-portkey Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final url as mentioned in the documentation is https://api.novita.ai/v3/openai/completions
But the above configuration will result in final url being https://api.novita.ai/v3/openai/v1/completions

Nothing in the documentation mentions /v1 being needed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey! I noticed that both https://api.novita.ai/v3/openai/v1/completions and https://api.novita.ai/v3/openai/completions are behaving similarly. But the novita's official documentation does not mention https://api.novita.ai/v3/openai/v1/completions being the end point as mentioned here https://novita.ai/get-started/llm.html#example-with-curl-client. Can you remove the /v1 so that it is compliant with their official documentation?

case 'chatComplete':
return '/v1/chat/completions';
AnyISalIn marked this conversation as resolved.
Show resolved Hide resolved
default:
return '';
}
},
};

export default NovitaAIApiConfig;
220 changes: 220 additions & 0 deletions src/providers/novita-ai/chatComplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { NOVITA_AI } from '../../globals';
import {
ChatCompletionResponse,
ErrorResponse,
ProviderConfig,
} from '../types';
import {
generateErrorResponse,
generateInvalidProviderResponseError,
} from '../utils';

// TODOS: 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.

export const NovitaAIChatCompleteConfig: ProviderConfig = {
AnyISalIn marked this conversation as resolved.
Show resolved Hide resolved
model: {
param: 'model',
required: true,
default: 'lzlv_70b',
},
messages: {
param: 'messages',
required: true,
default: '',
},
max_tokens: {
param: 'max_tokens',
required: true,
default: 128,
min: 1,
},
stop: {
param: 'stop',
},
temperature: {
param: 'temperature',
},
top_p: {
param: 'top_p',
},
n: {
param: 'n',
},
top_k: {
param: 'top_k',
},
presence_penalty: {
param: 'presence_penalty',
min: -2,
max: 2,
},
frequency_penalty: {
param: 'frequency_penalty',
min: -2,
max: 2,
},
stream: {
param: 'stream',
default: false,
},
logprobs: {
param: 'logprobs',
},
tools: {
param: 'tools',
},
tool_choice: {
param: 'tool_choice',
},
response_format: {
param: 'response_format',
},
};

export interface NovitaAIChatCompleteResponse extends ChatCompletionResponse {
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}

export interface NovitaAIErrorResponse {
model: string;
job_id: string;
request_id: string;
error: string;
message?: string;
type?: string;
}

export interface NovitaAIOpenAICompatibleErrorResponse extends ErrorResponse {}

export interface NovitaAIChatCompletionStreamChunk {
id: string;
request_id: string;
object: string;
choices: {
index: number;
delta: {
content: string;
};
}[];
}

export const NovitaAIErrorResponseTransform: (
response: NovitaAIErrorResponse | NovitaAIOpenAICompatibleErrorResponse
) => ErrorResponse | false = (response) => {
if ('error' in response && typeof response.error === 'string') {
return generateErrorResponse(
{ message: response.error, type: null, param: null, code: null },
NOVITA_AI
);
}

if ('error' in response && typeof response.error === 'object') {
return generateErrorResponse(
{
message: response.error?.message || '',
type: response.error?.type || null,
param: response.error?.param || null,
code: response.error?.code || null,
},
NOVITA_AI
);
}

if ('message' in response && response.message) {
return generateErrorResponse(
{
message: response.message,
type: response.type || null,
param: null,
code: null,
},
NOVITA_AI
);
}

return false;
};

export const NovitaAIChatCompleteResponseTransform: (
response:
| NovitaAIChatCompleteResponse
| NovitaAIErrorResponse
| NovitaAIOpenAICompatibleErrorResponse,
responseStatus: number
) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => {
if (responseStatus !== 200) {
const errorResponse = NovitaAIErrorResponseTransform(
response as NovitaAIErrorResponse
);
if (errorResponse) return errorResponse;
}

if ('choices' in response) {
return {
id: response.id,
object: response.object,
created: response.created,
model: response.model,
provider: NOVITA_AI,
choices: response.choices.map((choice) => {
return {
message: {
role: 'assistant',
content: choice.message.content,
tool_calls: choice.message.tool_calls
? choice.message.tool_calls.map((toolCall: any) => ({
id: toolCall.id,
type: toolCall.type,
function: toolCall.function,
}))
: null,
},
index: 0,
logprobs: null,
finish_reason: choice.finish_reason,
};
}),
usage: {
prompt_tokens: response.usage?.prompt_tokens,
completion_tokens: response.usage?.completion_tokens,
total_tokens: response.usage?.total_tokens,
},
};
}

return generateInvalidProviderResponseError(response, NOVITA_AI);
};

export const NovitaAIChatCompleteStreamChunkTransform: (
response: string
) => string = (responseChunk) => {
let chunk = responseChunk.trim();
chunk = chunk.replace(/^data: /, '');
chunk = chunk.trim();
if (chunk === '[DONE]') {
return `data: ${chunk}\n\n`;
}
const parsedChunk: NovitaAIChatCompletionStreamChunk = JSON.parse(chunk);
return (
`data: ${JSON.stringify({
id: parsedChunk.id,
object: parsedChunk.object,
created: Math.floor(Date.now() / 1000),
model: '',
AnyISalIn marked this conversation as resolved.
Show resolved Hide resolved
provider: NOVITA_AI,
choices: [
{
delta: {
content: parsedChunk.choices[0]?.delta.content,
},
index: 0,
finish_reason: '',
},
],
})}` + '\n\n'
);
};
Loading
Loading