Skip to content

Commit

Permalink
Add context.callApi (#37)
Browse files Browse the repository at this point in the history
* feat: add context.callApi

* fix: bump qstash and add resend test

* fix: bump typescript-eslint

* fix: add context.api.provider.call apis

* fix: bump qstash and review
  • Loading branch information
CahidArda authored Dec 23, 2024
1 parent 7396a5d commit c61794b
Show file tree
Hide file tree
Showing 11 changed files with 664 additions and 13 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@
"next": "^14.2.14",
"prettier": "3.3.3",
"tsup": "^8.3.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.8.0"
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.0"
},
"dependencies": {
"@upstash/qstash": "^2.7.17"
"@upstash/qstash": "^2.7.20"
},
"directories": {
"example": "examples"
Expand Down
53 changes: 53 additions & 0 deletions src/context/api/anthropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { anthropic } from "@upstash/qstash";
import { ApiCallSettings, BaseWorkflowApi } from "./base";
import { CallResponse } from "../../types";

type CreateChatCompletion = {
model: string;
messages: { role: "user" | "assistant"; content: unknown }[];
max_tokens: number;
metadata?: object;
stop_sequences?: string[];
/**
* streaming is not possible Upstash Workflow.
*/
stream?: false;
system?: string;
temparature?: number;
top_k?: number;
top_p?: number;
};

type ChatCompletion = {
id: string;
type: "message";
role: "assistant";
content: { type: "text"; text: string }[];
model: string;
stop_reasong: string;
stop_sequence: string[];
usage: unknown;
};

export class AnthropicAPI extends BaseWorkflowApi {
public async call<TResult = ChatCompletion, TBody = CreateChatCompletion>(
stepName: string,
settings: ApiCallSettings<
TBody,
{
token: string;
operation: "messages.create";
}
>
): Promise<CallResponse<TResult>> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { token, operation, ...parameters } = settings;
return await this.callApi<TResult, TBody>(stepName, {
api: {
name: "llm",
provider: anthropic({ token }),
},
...parameters,
});
}
}
49 changes: 49 additions & 0 deletions src/context/api/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CallResponse, CallSettings } from "../../types";
import { WorkflowContext } from "../context";
import { getProviderInfo } from "../provider";

export type ApiCallSettings<TBody = unknown, TFields extends object = object> = Omit<
CallSettings<TBody>,
"url"
> &
TFields;

export abstract class BaseWorkflowApi {
protected context: WorkflowContext;

constructor({ context }: { context: WorkflowContext }) {
this.context = context;
}

/**
* context.call which uses a QStash API
*
* @param stepName
* @param settings
* @returns
*/
protected async callApi<TResult = unknown, TBody = unknown>(
stepName: string,
settings: ApiCallSettings<
TBody,
{
api: Parameters<typeof getProviderInfo>[0];
}
>
): Promise<CallResponse<TResult>> {
const { url, appendHeaders, method } = getProviderInfo(settings.api);
const { method: userMethod, body, headers = {}, retries = 0, timeout } = settings;

return await this.context.call<TResult, TBody>(stepName, {
url,
method: userMethod ?? method,
body,
headers: {
...appendHeaders,
...headers,
},
retries,
timeout,
});
}
}
24 changes: 24 additions & 0 deletions src/context/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AnthropicAPI } from "./anthropic";
import { BaseWorkflowApi } from "./base";
import { OpenAIAPI } from "./openai";
import { ResendAPI } from "./resend";

export class WorkflowApi extends BaseWorkflowApi {
public get openai() {
return new OpenAIAPI({
context: this.context,
});
}

public get resend() {
return new ResendAPI({
context: this.context,
});
}

public get anthropic() {
return new AnthropicAPI({
context: this.context,
});
}
}
113 changes: 113 additions & 0 deletions src/context/api/openai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { openai } from "@upstash/qstash";
import { ApiCallSettings, BaseWorkflowApi } from "./base";
import { CallResponse } from "../../types";

type Messages =
| {
content: string;
role: "developer" | "system";
name?: string;
}
| {
content: unknown;
role: "user";
name?: string;
}
| {
content: unknown;
refusal?: string;
role: "assistant";
name?: string;
audio?: unknown;
tool_calls?: unknown;
}
| {
role: "tool";
content: string | unknown;
tool_call_id: string;
}
| {
role: "function";
content: string | undefined;
name: string;
};

type CreateChatCompletion = {
messages: Messages[];
model: string;
store?: boolean;
reasoning_effort?: string;
metadata?: unknown;
frequency_penalty?: number;
logit_bias?: Record<string, number>;
logprobs?: boolean;
top_logprobs?: number;
max_completion_tokens?: number;
n?: number;
modalities?: string[];
prediction?: unknown;
audio?: unknown;
presence_penalty?: number;
response_format?: unknown;
seed?: number;
service_tier?: string;
stop?: string | string[];
/**
* streaming is not supported in Upstash Workflow.
*/
stream?: false;
temperature?: number;
top_p?: number;
tools?: unknown;
tool_choice?: string;
parallel_tool_calls?: boolean;
user?: string;
};

type ChatCompletion = {
id: string;
choices: ChatCompletionChoice[];
created: number;
model: string;
object: "chat.completion";
service_tier?: "scale" | "default" | null;
system_fingerprint?: string;
usage?: unknown;
};

type ChatCompletionChoice = {
finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | "function_call";
index: number;
logprobs: unknown;
message: {
content: string | null;
refusal: string | null;
role: "assistant";
audio?: unknown;
tool_calls?: unknown;
};
};

export class OpenAIAPI extends BaseWorkflowApi {
public async call<TResult = ChatCompletion, TBody = CreateChatCompletion>(
stepName: string,
settings: ApiCallSettings<
TBody,
{
token: string;
organization?: string;
operation: "chat.completions.create";
}
>
): Promise<CallResponse<TResult>> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { token, organization, operation, ...parameters } = settings;
return await this.callApi<TResult, TBody>(stepName, {
api: {
name: "llm",
provider: openai({ token, organization }),
},
...parameters,
});
}
}
52 changes: 52 additions & 0 deletions src/context/api/resend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { resend } from "@upstash/qstash";
import { ApiCallSettings, BaseWorkflowApi } from "./base";
import { CallResponse } from "../../types";

type SendEmail = {
from: string;
to: string;
subject: string;
bcc?: string | string[];
cc?: string | string[];
scheduled_at?: string;
reply_to?: string | string[];
html?: string;
text?: string;
headers: unknown;
attachments: unknown;
tags: { name: string; value: string }[];
};
type SendEmailResponse = {
id: string;
};

type SendBatchEmail = SendEmail[];
type SendBatchEmailResponse = {
data: SendEmailResponse[];
};

export class ResendAPI extends BaseWorkflowApi {
public async call<
TBatch extends boolean = false,
TResult = TBatch extends true ? SendBatchEmailResponse : SendEmailResponse,
TBody = TBatch extends true ? SendBatchEmail : SendEmail,
>(
stepName: string,
settings: ApiCallSettings<
TBody,
{
token: string;
batch?: TBatch;
}
>
): Promise<CallResponse<TResult>> {
const { token, batch = false, ...parameters } = settings;
return await this.callApi<TResult, TBody>(stepName, {
api: {
name: "email",
provider: resend({ token, batch }),
},
...parameters,
});
}
}
Loading

0 comments on commit c61794b

Please sign in to comment.