Julep2Api,支持十几种模型、没有限制
研究了一下 https://docs.julep.ai/introduction/julep支持十几种模型,没有任何限制,模型还蛮多的
['mistral-large-2411', 'o1', 'text-embedding-3-large', 'vertex_ai/text-embedding-004', 'claude-3.5-haiku', 'cerebras/llama-4-scout-17b-16e-instruct', 'llama-3.1-8b', 'magnum-v4-72b', 'voyage-multilingual-2', 'claude-3-haiku', 'gpt-4o', 'BAAI/bge-m3', 'openrouter/meta-llama/llama-4-maverick', 'openrouter/meta-llama/llama-4-scout', 'claude-3.5-sonnet', 'hermes-3-llama-3.1-70b', 'claude-3.5-sonnet-20240620', 'qwen-2.5-72b-instruct', 'l3.3-euryale-70b', 'gpt-4o-mini', 'cerebras/llama-3.3-70b', 'o1-preview', 'gemini-1.5-pro-latest', 'l3.1-euryale-70b', 'claude-3-sonnet', 'Alibaba-NLP/gte-large-en-v1.5', 'openrouter/meta-llama/llama-4-scout:free', 'llama-3.1-70b', 'eva-qwen-2.5-72b', 'claude-3.5-sonnet-20241022', 'gemini-2.0-flash', 'deepseek-chat', 'o1-mini', 'eva-llama-3.33-70b', 'gemini-2.5-pro-preview-03-25', 'gemini-1.5-pro', 'gpt-4-turbo', 'openrouter/meta-llama/llama-4-maverick:free', 'o3-mini', 'claude-3.7-sonnet', 'voyage-3', 'cerebras/llama-3.1-8b', 'claude-3-opus']
试了下找接口地址组合了一下,但似乎会收到 Internal Server Error?上代码!
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
const JULEP_API_BASE_URL = "https://api.julep.ai/api";
// Simple function to generate a UUID (for Agent and Session creation)
function generateUuid(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(//g, function(c) {
const r = Math.random() * 16 | 0, v = c === "x" ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// List of supported models (from your provided list)
const supportedModels = [
'mistral-large-2411', 'o1', 'text-embedding-3-large', 'vertex_ai/text-embedding-004',
'claude-3.5-haiku', 'cerebras/llama-4-scout-17b-16e-instruct', 'llama-3.1-8b',
'magnum-v4-72b', 'voyage-multilingual-2', 'claude-3-haiku', 'gpt-4o', 'BAAI/bge-m3',
'openrouter/meta-llama/llama-4-maverick', 'openrouter/meta-llama/llama-4-scout',
'claude-3.5-sonnet', 'hermes-3-llama-3.1-70b', 'claude-3.5-sonnet-20240620',
'qwen-2.5-72b-instruct', 'l3.3-euryale-70b', 'gpt-4o-mini', 'cerebras/llama-3.3-70b',
'o1-preview', 'gemini-1.5-pro-latest', 'l3.1-euryale-70b', 'claude-3-sonnet',
'Alibaba-NLP/gte-large-en-v1.5', 'openrouter/meta-llama/llama-4-scout:free',
'llama-3.1-70b', 'eva-qwen-2.5-72b', 'claude-3.5-sonnet-20241022', 'gemini-2.0-flash',
'deepseek-chat', 'o1-mini', 'eva-llama-3.33-70b', 'gemini-2.5-pro-preview-03-25',
'gemini-1.5-pro', 'gpt-4-turbo', 'openrouter/meta-llama/llama-4-maverick:free',
'o3-mini', 'claude-3.7-sonnet', 'voyage-3', 'cerebras/llama-3.1-8b', 'claude-3-opus'
];
async function handler(req: Request): Promise<Response> {
const url = new URL(req.url);
// Handle /v1/models endpoint
if (url.pathname === "/v1/models") {
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
return listModelsHandler();
}
// Handle /v1/chat/completions endpoint
if (url.pathname === "/v1/chat/completions") {
if (req.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
return chatCompletionsHandler(req);
}
// Return 404 for other paths
return new Response("Not Found", { status: 404 });
}
// Handler for the /v1/models endpoint
function listModelsHandler(): Response {
const models = supportedModels.map(modelId => ({
id: modelId,
object: "model",
created: Math.floor(Date.now() / 1000), // Use current time
owned_by: "julep-proxy", // Indicate this proxy owns the listing
// Add other relevant fields if needed, like permission
}));
const responseBody = {
object: "list",
data: models,
};
return new Response(JSON.stringify(responseBody), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
// Handler for the /v1/chat/completions endpoint
async function chatCompletionsHandler(req: Request): Promise<Response> {
const headers = new Headers(req.headers);
headers.delete("content-length");
if (!headers.has("Authorization")) {
return new Response("Authorization header is required.", { status: 401 });
}
try {
const openaiPayload = await req.json();
// 1. Create a new Agent for this session
const agentId = generateUuid();
const createAgentPayload = {
name: `temp-agent-${agentId}`, // Use a unique name
about: "Temporary agent created for a chat session."
// Add other default agent properties if desired
};
const createAgentResponse = await fetch(`${JULEP_API_BASE_URL}/agents/${agentId}`, {
method: "POST",
headers: headers,
body: JSON.stringify(createAgentPayload),
});
if (!createAgentResponse.ok) {
console.error("Failed to create agent:", await createAgentResponse.text());
return new Response("Failed to initialize chat session (agent creation failed).", { status: createAgentResponse.status });
}
// const agent = await createAgentResponse.json(); // Agent details if needed
// 2. Create a new Session using the created Agent
const sessionId = generateUuid(); // Use UUID for Julep Session ID path parameter
const createSessionPayload = {
agent: agentId, // Link the session to the newly created agent
// Add other default session properties if desired
// user: "user-id-if-known", // Optionally link to a user
};
// If the client provided a model, include it in the session creation request
if (openaiPayload.model) {
// Note: Julep's session creation doesn't directly take a model parameter,
// the model is typically associated with the Agent.
// If you need to force a specific model, you might need to update the Agent
// after creation, or rely on the chat request's model parameter.
// For now, we'll just create the session and let the chat request specify the model.
}
const createSessionResponse = await fetch(`${JULEP_API_BASE_URL}/sessions/${sessionId}`, {
method: "POST",
headers: headers,
body: JSON.stringify(createSessionPayload),
});
if (!createSessionResponse.ok) {
console.error("Failed to create session:", await createSessionResponse.text());
// Clean up the created agent? This adds complexity. For now, just fail.
return new Response("Failed to initialize chat session (session creation failed).", { status: createSessionResponse.status });
}
// const session = await createSessionResponse.json(); // Session details if needed
// 3. Initiate the chat using the new session ID (which is now a UUID)
const julepPayload = convertOpenaiToJulep(openaiPayload);
const julepUrl = `${JULEP_API_BASE_URL}/sessions/${sessionId}/chat`; // Use the UUID session ID here
const julepResponse = await fetch(julepUrl, {
method: "POST",
headers: headers,
body: JSON.stringify(julepPayload),
});
// Handle streaming response
if (openaiPayload.stream && julepResponse.headers.get("content-type")?.includes("text/event-stream")) {
const readableStream = new ReadableStream({
async start(controller) {
const reader = julepResponse.body?.getReader();
if (!reader) {
controller.error("Failed to get reader from Julep response.");
return;
}
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete lines (events)
const lines = buffer.split('\n');
buffer = lines.pop() || ""; // Keep the last potentially incomplete line
for (const line of lines) {
if (line.startsWith("data:")) {
const data = line.substring(5).trim(); // Extract data after "data:"
if (data === "") {
controller.enqueue(`data: \n\n`);
} else {
try {
const julepChunk = JSON.parse(data);
// Use the Julep session ID (UUID) as the OpenAI response ID
const openaiChunk = convertJulepChunkToOpenai(julepChunk, openaiPayload.model || "julep-model", sessionId);
controller.enqueue(`data: ${JSON.stringify(openaiChunk)}\n\n`);
} catch (parseError) {
console.error("Error parsing Julep stream chunk:", parseError);
// Optionally enqueue an error chunk or log it
}
}
} else if (line.startsWith(":")) {
// Ignore comments
} else {
// Handle other event types if necessary, though "data" is typical for chat streams
console.warn("Received unexpected stream line:", line);
}
}
}
} catch (error) {
console.error("Error reading Julep stream:", error);
controller.error(error);
} finally {
reader.releaseLock();
controller.close();
}
},
});
return new Response(readableStream, {
status: julepResponse.status,
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
} else {
// Handle non-streaming response
const julepData = await julepResponse.json();
// Use the Julep session ID (UUID) as the OpenAI response ID
const openaiResponse = convertJulepToOpenai(julepData, openaiPayload.model || "julep-model", sessionId);
return new Response(JSON.stringify(openaiResponse), {
status: julepResponse.status,
headers: {
"Content-Type": "application/json",
},
});
}
} catch (error) {
console.error("Error processing request:", error);
return new Response("Internal Server Error", { status: 500 });
}
}
function convertOpenaiToJulep(openaiPayload: any): any {
const julepPayload: any = {
messages: openaiPayload.messages.map((msg: any) => ({
role: msg.role,
content: msg.content,
name: msg.name,
tool_call_id: msg.tool_call_id,
tool_calls: msg.tool_calls ? msg.tool_calls.map((tc: any) => ({
type: tc.type,
function: tc.function ? {
name: tc.function.name,
arguments: tc.function.arguments
} : undefined,
integration: tc.integration,
system: tc.system,
api_call: tc.api_call,
computer_20241022: tc.computer_20241022,
text_editor_20241022: tc.text_editor_20241022,
bash_20241022: tc.bash_20241022,
id: tc.id,
})) : undefined,
})),
tools: openaiPayload.tools,
tool_choice: openaiPayload.tool_choice,
recall: openaiPayload.recall,
save: openaiPayload.save,
model: openaiPayload.model,
stream: openaiPayload.stream,
stop: openaiPayload.stop,
seed: openaiPayload.seed,
max_tokens: openaiPayload.max_tokens,
logit_bias: openaiPayload.logit_bias,
response_format: openaiPayload.response_format,
agent: openaiPayload.agent,
repetition_penalty: openaiPayload.repetition_penalty,
length_penalty: openaiPayload.length_penalty,
min_p: openaiPayload.min_p,
frequency_penalty: openaiPayload.frequency_penalty,
presence_penalty: openaiPayload.presence_penalty,
temperature: openaiPayload.temperature,
top_p: openaiPayload.top_p,
};
// Remove session_id from the payload if it was mistakenly included
delete julepPayload.session_id;
return julepPayload;
}
// Converts a non-streaming Julep response to OpenAI format
// Added sessionId parameter to use as the OpenAI response ID
function convertJulepToOpenai(julepData: any, model: string, sessionId: string): any {
const openaiResponse: any = {
id: sessionId, // Use the Julep session ID (UUID) as the OpenAI response ID
object: "chat.completion",
created: Math.floor(new Date(julepData.created_at).getTime() / 1000),
model: model,
choices: julepData.choices.map((choice: any) => ({
index: choice.index,
// *** Fix: Extract message content from choice.message.content ***
message: {
role: choice.message?.role || "assistant", // Use choice.message.role
content: choice.message?.content || "", // Use choice.message.content
tool_calls: choice.message?.tool_calls ? choice.message.tool_calls.map((tc: any) => ({
id: tc.id,
type: tc.type,
function: tc.function ? {
name: tc.function.name,
arguments: tc.function.arguments
} : undefined,
})) : undefined,
},
finish_reason: choice.finish_reason,
// logprobs are not typically in non-streaming response choices
})),
usage: julepData.usage ? {
prompt_tokens: julepData.usage.prompt_tokens,
completion_tokens: julepData.usage.completion_tokens,
total_tokens: julepData.usage.total_tokens,
} : undefined,
};
return openaiResponse;
}
// Converts a single Julep streaming chunk to OpenAI streaming format
function convertJulepChunkToOpenai(julepChunk: any, model: string, sessionId: string): any {
const openaiChunk: any = {
id: sessionId,
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: model,
choices: julepChunk.choices.map((choice: any) => {
const openaiChoice: any = {
index: choice.index,
// Delta structure for streaming chunks
delta: {
role: choice.delta?.role,
content: choice.delta?.content,
tool_calls: choice.delta?.tool_calls ? choice.delta.tool_calls.map((tc: any) => ({
id: tc.id,
type: tc.type,
function: tc.function ? {
name: tc.function.name,
arguments: tc.function.arguments
} : undefined,
})) : undefined,
},
finish_reason: choice.finish_reason,
// logprobs are not typically in streaming chunks
};
// Clean up empty delta fields
if (openaiChoice.delta.role === undefined) delete openaiChoice.delta.role;
if (openaiChoice.delta.content === undefined) delete openaiChoice.delta.content;
if (openaiChoice.delta.tool_calls === undefined) delete openaiChoice.delta.tool_calls;
if (Object.keys(openaiChoice.delta).length === 0 && openaiChoice.finish_reason === undefined) {
delete openaiChoice.delta;
}
return openaiChoice;
}),
};
return openaiChunk;
}
// Start the server
serve(handler);
自带 API Key,在 https://dashboard.julep.ai/ 点右下角按钮获取
页:
[1]