@agentforce/adk
Version:
AgentForce Agent Development Kit - A powerful framework for building AI agents and servers
245 lines (219 loc) • 10.2 kB
text/typescript
import { Hono } from "hono";
import type { Context } from "hono";
import ollama from "ollama";
/**
* Models route handler for /v1/models endpoints
* Returns a list of available models in OpenAI-compatible format
*
* Endpoints:
* - GET /models - List all available models
* - GET /models/{model} - Get specific model information by ID
*
* Features:
* - Fetches actual Ollama models when provider is "ollama" using ollama.list()
* - Normalizes model IDs by replacing "/" with "-" for URL compatibility
* - Includes current model configuration in the response
* - Falls back to common models for non-Ollama providers
* - Returns additional Ollama-specific metadata (size, digest, details)
* - Handles Ollama connection errors gracefully
* - Supports model-specific lookups with proper 404 handling
* - Preserves original model names in the "root" field
*
* URL Compatibility:
* - Model names with "/" (e.g., "huihui_ai/mistral-small-abliterated:latest")
* are returned with ID "huihui_ai-mistral-small-abliterated:latest"
* - Original names are preserved in the "root" field
* - Lookup supports: normalized IDs (dashes), URL-encoded names (%2F), and original names
* - Use normalized IDs for URL-safe API calls: /v1/models/huihui_ai-mistral-small-abliterated:latest
* - Or use URL-encoded slashes: /v1/models/huihui_ai%2Fmistral-small-abliterated:latest
*
* @param currentModel - The current model configured in the agent
* @param currentProvider - The current provider configured in the agent
* @returns Hono app with /models and /models/{model} endpoints
*/
export function createModelsRoute(currentModel: string | null, currentProvider: string | null): Hono {
const app = new Hono();
app.get("/models", async (c: Context) => {
let modelData = [];
// Always include the current model if available
if (currentModel && currentProvider) {
modelData.push({
id: currentModel,
object: "model",
created: Math.floor(Date.now() / 1000),
owned_by: currentProvider,
permission: [],
root: currentModel,
parent: null,
});
}
// Fetch Ollama models if provider is ollama
if (currentProvider === "ollama") {
try {
const ollamaResponse = await ollama.list();
// Convert Ollama models to OpenAI-compatible format
const ollamaModels = ollamaResponse.models.map(model => ({
id: model.name.replace(/\//g, "-"), // Replace forward slashes with dashes for URL compatibility
object: "model",
created: Math.floor(new Date(model.modified_at).getTime() / 1000),
owned_by: "ollama",
permission: [],
root: model.name, // Keep original name in root field
parent: null,
// Additional Ollama-specific metadata
size: model.size,
digest: model.digest,
details: model.details,
}));
// Add Ollama models to the data array (avoid duplicates)
ollamaModels.forEach(ollamaModel => {
if (!modelData.some(existingModel => existingModel.id === ollamaModel.id)) {
modelData.push(ollamaModel);
}
});
console.log(`✅ Fetched ${ollamaModels.length} Ollama models`);
} catch (error) {
console.error(`⚠️ Failed to fetch Ollama models: ${error}`);
// Fallback to current model only if Ollama is not available
if (modelData.length === 0) {
modelData.push({
id: "ollama-unavailable",
object: "model",
created: Math.floor(Date.now() / 1000),
owned_by: "ollama",
permission: [],
root: "ollama-unavailable",
parent: null,
});
}
}
} else {
// Add some common models for other providers as fallback
const fallbackModels = [
{
id: "gpt-4",
object: "model",
created: 1687882411,
owned_by: "openai",
permission: [],
root: "gpt-4",
parent: null,
},
{
id: "gpt-3.5-turbo",
object: "model",
created: 1677610602,
owned_by: "openai",
permission: [],
root: "gpt-3.5-turbo",
parent: null,
},
];
// Add fallback models (avoid duplicates)
fallbackModels.forEach(fallbackModel => {
if (!modelData.some(existingModel => existingModel.id === fallbackModel.id)) {
modelData.push(fallbackModel);
}
});
}
const modelsResponse = {
object: "list",
data: modelData,
};
return c.json(modelsResponse);
});
app.get("/models/:model", async (c: Context) => {
const modelId = c.req.param("model");
if (!modelId) {
return c.json({ error: "Model ID is required" }, 400);
}
// Check if it's the current model first (handle normalized, URL-encoded, and original names)
if (currentModel) {
const decodedModelId = decodeURIComponent(modelId);
const normalizedCurrentModel = currentModel.replace(/\//g, "-");
if (currentModel === modelId || // Direct match with original name
currentModel === decodedModelId || // Match URL-decoded name
normalizedCurrentModel === modelId || // Match normalized name
normalizedCurrentModel === decodedModelId // Match normalized decoded name
) {
const modelInfo = {
id: currentModel.replace(/\//g, "-"), // Return normalized ID
object: "model",
created: Math.floor(Date.now() / 1000),
owned_by: currentProvider || "agentforce",
permission: [],
root: currentModel, // Keep original name in root
parent: null,
};
return c.json(modelInfo);
}
}
// If provider is Ollama, search through Ollama models
if (currentProvider === "ollama") {
try {
const ollamaResponse = await ollama.list();
// Find the specific model by checking both normalized ID and original name
// The incoming modelId could be either the normalized version (with dashes) or URL-encoded (with %2F)
const decodedModelId = decodeURIComponent(modelId); // Handle URL-encoded slashes (%2F)
const foundModel = ollamaResponse.models.find(model =>
model.name === modelId || // Direct match with original name
model.name === decodedModelId || // Match URL-decoded name
model.name.replace(/\//g, "-") === modelId || // Match normalized name
model.name.replace(/\//g, "-") === decodedModelId, // Match normalized decoded name
);
if (foundModel) {
const modelInfo = {
id: foundModel.name.replace(/\//g, "-"), // Return normalized ID
object: "model",
created: Math.floor(new Date(foundModel.modified_at).getTime() / 1000),
owned_by: "ollama",
permission: [],
root: foundModel.name,
parent: null,
// Additional Ollama-specific metadata
size: foundModel.size,
digest: foundModel.digest,
details: foundModel.details,
};
console.log(`✅ Found Ollama model: ${modelId}`);
return c.json(modelInfo);
}
console.log(`⚠️ Ollama model not found: ${modelId}`);
return c.json({ error: `Model '${modelId}' not found` }, 404);
} catch (error) {
console.error(`⚠️ Failed to fetch Ollama model ${modelId}: ${error}`);
return c.json({ error: "Failed to fetch model information" }, 500);
}
} else {
// For non-Ollama providers, check against known models
const knownModels = [
{
id: "gpt-4",
object: "model",
created: 1687882411,
owned_by: "openai",
permission: [],
root: "gpt-4",
parent: null,
},
{
id: "gpt-3.5-turbo",
object: "model",
created: 1677610602,
owned_by: "openai",
permission: [],
root: "gpt-3.5-turbo",
parent: null,
},
];
const foundModel = knownModels.find(model => model.id === modelId);
if (foundModel) {
console.log(`✅ Found ${currentProvider || "fallback"} model: ${modelId}`);
return c.json(foundModel);
}
console.log(`⚠️ Model not found: ${modelId}`);
return c.json({ error: `Model '${modelId}' not found` }, 404);
}
});
return app;
}