Skip to content

Commit

Permalink
Formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
justyns committed Jan 28, 2025
1 parent 1e471f3 commit cce75cc
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 35 deletions.
1 change: 1 addition & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This page is a brief overview of each version.
- Better logging when SSE events have errors
- Add support for retrieving list of models from openai and ollama providers
- Add a Connectivity Test command and page to test whether an api is working
- Add experimental support for proxying all LLM calls through the silverbullet server

---
## 0.4.1 (2024-11-15)
Expand Down
2 changes: 1 addition & 1 deletion silverbullet-ai.plug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ functions:
path: src/proxyHandler.ts:proxyHandler
events:
- http:request:/ai-proxy/*

aiPromptSlashCommplete:
path: src/prompts.ts:aiPromptSlashComplete
events:
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/EmbeddingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export abstract class AbstractEmbeddingProvider
baseUrl: string,
modelName: string,
requireAuth: boolean = true,
proxyOnServer?: boolean
proxyOnServer?: boolean,
) {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
Expand All @@ -42,11 +42,11 @@ export abstract class AbstractEmbeddingProvider

protected getUrl(path: string): string {
// Remove any leading slashes from the path
path = path.replace(/^\/+/, '');
path = path.replace(/^\/+/, "");

if (this.proxyOnServer) {
// Remove any v1 prefix from the path if it exists
path = path.replace(/^v1\//, '');
path = path.replace(/^v1\//, "");
return `/_/ai-proxy/${this.fullName}/${path}`;
} else {
return `${this.baseUrl}/${path}`;
Expand Down
7 changes: 5 additions & 2 deletions src/interfaces/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ export abstract class AbstractProvider implements ProviderInterface {

protected getUrl(path: string): string {
// Remove leading slashes from the path
path = path.replace(/^\/+/, '');
path = path.replace(/^\/+/, "");

if (this.proxyOnServer) {
console.log("Proxy on server, using proxy URL:", `/_/ai-proxy/${this.fullName}/${path}`);
console.log(
"Proxy on server, using proxy URL:",
`/_/ai-proxy/${this.fullName}/${path}`,
);
return `/_/ai-proxy/${this.fullName}/${path}`;
} else {
return `${this.baseUrl}/${path}`;
Expand Down
12 changes: 9 additions & 3 deletions src/providers/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export class GeminiProvider extends AbstractProvider {
const { messages, onDataReceived } = options;

try {
const sseUrl = this.getUrl(`v1beta/models/${this.modelName}:streamGenerateContent?key=${this.apiKey}&alt=sse`);
const sseUrl = this.getUrl(
`v1beta/models/${this.modelName}:streamGenerateContent?key=${this.apiKey}&alt=sse`,
);

const headers: HttpHeaders = {
"Content-Type": "application/json",
Expand Down Expand Up @@ -165,7 +167,9 @@ export class GeminiProvider extends AbstractProvider {
);

const response = await nativeFetch(
this.getUrl(`v1beta/models/${this.modelName}:generateContent?key=${this.apiKey}`),
this.getUrl(
`v1beta/models/${this.modelName}:generateContent?key=${this.apiKey}`,
),
{
method: "POST",
headers: {
Expand Down Expand Up @@ -213,7 +217,9 @@ export class GeminiEmbeddingProvider extends AbstractEmbeddingProvider {
}

const response = await nativeFetch(
this.getUrl(`v1beta/models/${this.modelName}:embedContent?key=${this.apiKey}`),
this.getUrl(
`v1beta/models/${this.modelName}:embedContent?key=${this.apiKey}`,
),
{
method: "POST",
headers: headers,
Expand Down
4 changes: 2 additions & 2 deletions src/providers/ollama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class OllamaProvider extends AbstractProvider {

// List models api isn't behind /v1/ like the other endpoints, but we don't want to force the user to change the config yet
const response = await nativeFetch(
this.getUrl('api/tags').replace(/\/v1\/?/, ''),
this.getUrl("api/tags").replace(/\/v1\/?/, ""),
{
method: "GET",
headers: headers,
Expand Down Expand Up @@ -113,7 +113,7 @@ export class OllamaEmbeddingProvider extends AbstractEmbeddingProvider {
}

const response = await nativeFetch(
this.getUrl('api/embeddings'),
this.getUrl("api/embeddings"),
{
method: "POST",
headers: headers,
Expand Down
8 changes: 4 additions & 4 deletions src/providers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class OpenAIProvider extends AbstractProvider {
const { messages, onDataReceived, onResponseComplete } = options;

try {
const sseUrl = this.getUrl('chat/completions');
const sseUrl = this.getUrl("chat/completions");

const headers: HttpHeaders = {
"Content-Type": "application/json",
Expand Down Expand Up @@ -135,7 +135,7 @@ export class OpenAIProvider extends AbstractProvider {
}

const response = await nativeFetch(
this.getUrl('models'),
this.getUrl("models"),
{
method: "GET",
headers: headers,
Expand Down Expand Up @@ -173,7 +173,7 @@ export class OpenAIProvider extends AbstractProvider {
};

const response = await nativeFetch(
this.getUrl('chat/completions'),
this.getUrl("chat/completions"),
{
method: "POST",
headers: headers,
Expand Down Expand Up @@ -232,7 +232,7 @@ export class OpenAIEmbeddingProvider extends AbstractEmbeddingProvider {
}

const response = await nativeFetch(
this.getUrl('embeddings'),
this.getUrl("embeddings"),
{
method: "POST",
headers: headers,
Expand Down
67 changes: 47 additions & 20 deletions src/proxyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import type {
} from "@silverbulletmd/silverbullet/types";
import { Provider } from "./types.ts";

export async function proxyHandler(request: EndpointRequest): Promise<Response | EndpointResponse> {
export async function proxyHandler(
request: EndpointRequest,
): Promise<Response | EndpointResponse> {
// TODO: Add a token or something to restrict access to this endpoint
console.log("ai:proxyHandler called to handle path:", request.path);
// Find the model name from the path (e.g., /ai-proxy/ollama-localhost-proxy/chat/completions)
const pathParts = request.path.split("/");
Expand All @@ -29,47 +32,63 @@ export async function proxyHandler(request: EndpointRequest): Promise<Response |

// Find the model configuration that matches the requested model name
const modelConfig = aiSettings.textModels.find(
(model: ModelConfig) => model.name === modelName
(model: ModelConfig) => model.name === modelName,
);

if (!modelConfig) {
return new Response(`Model '${modelName}' not found in configuration`, { status: 404 });
return new Response(`Model '${modelName}' not found in configuration`, {
status: 404,
});
}

// Check if proxy is enabled for this model
if (modelConfig.proxyOnServer === false) {
return new Response(`Proxy is disabled for model '${modelName}'`, { status: 403 });
return new Response(`Proxy is disabled for model '${modelName}'`, {
status: 403,
});
}

// TODO: Confirm this doesn't overwrite the user's selected model
await configureSelectedModel(modelConfig);
let baseUrl = modelConfig.baseUrl;

console.log("proxyHandler baseUrl:", baseUrl, "provider:", modelConfig.provider, "remainingPath:", remainingPath);
console.log(
"proxyHandler baseUrl:",
baseUrl,
"provider:",
modelConfig.provider,
"remainingPath:",
remainingPath,
);
// TODO: switch completions for ollama to use ollama api instead of openai api?
if (modelConfig.provider === Provider.Ollama) {
// For the list models endpoint, use /api/ without v1 prefix
if (remainingPath.includes("models") || remainingPath == "api/tags") {
baseUrl = baseUrl.replace(/v1\/?/, '');
baseUrl = baseUrl.replace(/v1\/?/, "");
} else {
// Everything else should use openai-compatible endpoints under /v1/
baseUrl = baseUrl.replace(/\/v1\/?$/, '') + '/v1/';
baseUrl = baseUrl.replace(/\/v1\/?$/, "") + "/v1/";
}
console.log("New baseUrl for ollama:", baseUrl);
}

// Use the baseUrl from the model config and append the remaining path
const apiUrl = baseUrl.endsWith('/') ? `${baseUrl}${remainingPath}` : `${baseUrl}/${remainingPath}`;
const apiUrl = baseUrl.endsWith("/")
? `${baseUrl}${remainingPath}`
: `${baseUrl}/${remainingPath}`;

// Forward the request to the appropriate LLM API with original headers
const requestHeaders: Record<string, string> = {
"Content-Type": "application/json",
"Accept": "*/*",
};

// Copy other relevant headers, but skip problematic ones
for (const [key, value] of Object.entries(request.headers)) {
if (!["host", "connection", "origin", "content-type", "content-length"].includes(key.toLowerCase())) {
if (
!["host", "connection", "origin", "content-type", "content-length"]
.includes(key.toLowerCase())
) {
requestHeaders[key] = value as string;
}
}
Expand All @@ -78,7 +97,9 @@ export async function proxyHandler(request: EndpointRequest): Promise<Response |
let requestBody: any = undefined;
if (request.body) {
try {
requestBody = typeof request.body === 'string' ? JSON.parse(request.body) : request.body;
requestBody = typeof request.body === "string"
? JSON.parse(request.body)
: request.body;
} catch (error) {
console.error("Error processing request body:", error);
// Only return error for endpoints that require a body
Expand All @@ -93,8 +114,11 @@ export async function proxyHandler(request: EndpointRequest): Promise<Response |

if (isStreamingRequest) {
console.log("proxyHandler handling streaming request");
console.log("Request body before sending:", JSON.stringify(requestBody, null, 2));

console.log(
"Request body before sending:",
JSON.stringify(requestBody, null, 2),
);

// For streaming requests, set appropriate headers
requestHeaders["Accept"] = "text/event-stream";
requestHeaders["Cache-Control"] = "no-cache";
Expand All @@ -111,36 +135,39 @@ export async function proxyHandler(request: EndpointRequest): Promise<Response |
const { readable, writable } = new TransformStream();
response.body.pipeTo(writable);


if (!response.ok) {
const errorText = await response.text();
console.log("Error from LLM API:", errorText);
return new Response(`Failed to fetch from LLM API: ${errorText}`, { status: response.status });
return new Response(`Failed to fetch from LLM API: ${errorText}`, {
status: response.status,
});
}

// Instead of returning the stream directly, return a special response
return {
status: response.status,
headers: {
...Object.fromEntries(response.headers.entries()),
'content-type': 'text/event-stream',
"content-type": "text/event-stream",
},
// Send the response as an object that can be serialized
body: {
__type: 'STREAM_PROXY',
__type: "STREAM_PROXY",
url: apiUrl,
method: request.method,
headers: requestHeaders,
requestBody: requestBody
}
requestBody: requestBody,
},
};
}

// For non-streaming requests, use regular fetch
const response = await nativeFetch(apiUrl, {
method: request.method,
headers: requestHeaders,
body: request.method !== "GET" && request.method !== "HEAD" && requestBody ? JSON.stringify(requestBody) : undefined,
body: request.method !== "GET" && request.method !== "HEAD" && requestBody
? JSON.stringify(requestBody)
: undefined,
});

if (!response.ok) {
Expand Down

0 comments on commit cce75cc

Please sign in to comment.