fix(catalog): marked cerebras gemma vision capable
Mapped Cerebras gemma-4-31b dynamic discovery to include image input capability and covered the OpenAI Chat Completions image_url serialization path. Fixes #3854
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test";
|
||||
import { convertMessages } from "@oh-my-pi/pi-ai/providers/openai-completions";
|
||||
import { NON_VISION_IMAGE_PLACEHOLDER } from "@oh-my-pi/pi-ai/providers/vision-guard";
|
||||
import type { AssistantMessage, Context, Model, ToolResultMessage, Usage } from "@oh-my-pi/pi-ai/types";
|
||||
import { buildModel } from "@oh-my-pi/pi-catalog/build";
|
||||
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
|
||||
import type { ResolvedOpenAICompat } from "@oh-my-pi/pi-catalog/types";
|
||||
|
||||
@@ -75,6 +76,48 @@ function buildToolResult(toolCallId: string, timestamp: number): ToolResultMessa
|
||||
}
|
||||
|
||||
describe("openai-completions convertMessages", () => {
|
||||
it("serializes Cerebras gemma image inputs as Chat Completions data URIs", () => {
|
||||
const model = buildModel({
|
||||
id: "gemma-4-31b",
|
||||
name: "Gemma 4 31B",
|
||||
api: "openai-completions",
|
||||
provider: "cerebras",
|
||||
baseUrl: "https://api.cerebras.ai/v1",
|
||||
reasoning: false,
|
||||
input: ["text", "image"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 8_192,
|
||||
});
|
||||
const context: Context = {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Identify the shapes and colors. Return JSON only." },
|
||||
{ type: "image", mimeType: "image/png", data: "ZmFrZQ==" },
|
||||
],
|
||||
timestamp: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const messages = convertMessages(model, context, compat);
|
||||
|
||||
expect(messages).toEqual([
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Identify the shapes and colors. Return JSON only." },
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: { url: "data:image/png;base64,ZmFrZQ==" },
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("batches tool-result images after consecutive tool results", () => {
|
||||
const baseModel = getBundledModel("openai", "gpt-4o-mini") as Model<"openai-completions">;
|
||||
const model: Model<"openai-completions"> = {
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Cerebras `gemma-4-31b` dynamic discovery to mark the model as image-capable so attached images are serialized as OpenAI Chat Completions `image_url` data URIs. ([#3854](https://github.com/can1357/oh-my-pi/issues/3854))
|
||||
|
||||
## [16.2.6] - 2026-06-29
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -803,6 +803,18 @@ export function groqModelManagerOptions(config?: GroqModelManagerConfig): ModelM
|
||||
// 3. Cerebras
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const CEREBRAS_IMAGE_INPUT_MODEL_IDS = new Set(["gemma-4-31b"]);
|
||||
|
||||
function applyCerebrasDiscoveryOverrides(model: ModelSpec<"openai-completions">): ModelSpec<"openai-completions"> {
|
||||
if (!CEREBRAS_IMAGE_INPUT_MODEL_IDS.has(model.id)) {
|
||||
return model;
|
||||
}
|
||||
return {
|
||||
...model,
|
||||
input: ["text", "image"],
|
||||
};
|
||||
}
|
||||
|
||||
export interface CerebrasModelManagerConfig {
|
||||
apiKey?: string;
|
||||
baseUrl?: string;
|
||||
@@ -812,7 +824,27 @@ export interface CerebrasModelManagerConfig {
|
||||
export function cerebrasModelManagerOptions(
|
||||
config?: CerebrasModelManagerConfig,
|
||||
): ModelManagerOptions<"openai-completions"> {
|
||||
return createSimpleOpenAICompletionsOptions("cerebras", "https://api.cerebras.ai/v1", config);
|
||||
const apiKey = config?.apiKey;
|
||||
const baseUrl = config?.baseUrl ?? "https://api.cerebras.ai/v1";
|
||||
const references = createBundledReferenceMap<"openai-completions">("cerebras");
|
||||
return {
|
||||
providerId: "cerebras",
|
||||
...(apiKey && {
|
||||
fetchDynamicModels: () =>
|
||||
fetchOpenAICompatibleModels({
|
||||
api: "openai-completions",
|
||||
provider: "cerebras",
|
||||
baseUrl,
|
||||
apiKey,
|
||||
mapModel: (entry, defaults) => {
|
||||
const reference = references.get(defaults.id);
|
||||
const model = mapWithBundledReference(entry, defaults, reference);
|
||||
return applyCerebrasDiscoveryOverrides(model);
|
||||
},
|
||||
fetch: config?.fetch,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { cerebrasModelManagerOptions } from "@oh-my-pi/pi-catalog/provider-models/openai-compat";
|
||||
import type { FetchImpl } from "@oh-my-pi/pi-catalog/types";
|
||||
|
||||
describe("Cerebras provider discovery", () => {
|
||||
test("discovers gemma-4-31b as image-capable", async () => {
|
||||
const calls: Array<{ url: string; authorization: string | null }> = [];
|
||||
const fetchMock: FetchImpl = async (input: string | URL | Request, init?: RequestInit) => {
|
||||
const headers = new Headers(init?.headers);
|
||||
calls.push({
|
||||
url: String(input),
|
||||
authorization: headers.get("authorization"),
|
||||
});
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
data: [
|
||||
{ id: "gemma-4-31b", object: "model" },
|
||||
{ id: "llama3.1-8b", object: "model" },
|
||||
],
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
};
|
||||
|
||||
const options = cerebrasModelManagerOptions({ apiKey: "cerebras-test-key", fetch: fetchMock });
|
||||
const models = await options.fetchDynamicModels?.();
|
||||
|
||||
expect(calls).toEqual([
|
||||
{
|
||||
url: "https://api.cerebras.ai/v1/models",
|
||||
authorization: "Bearer cerebras-test-key",
|
||||
},
|
||||
]);
|
||||
expect(models?.find(model => model.id === "gemma-4-31b")).toMatchObject({
|
||||
provider: "cerebras",
|
||||
api: "openai-completions",
|
||||
input: ["text", "image"],
|
||||
});
|
||||
expect(models?.find(model => model.id === "llama3.1-8b")?.input).toEqual(["text"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user