feat(experiments): support freeform provider responses
This commit is contained in:
+57
-18
@@ -352,6 +352,9 @@ class OllamaLlmProvider {
|
||||
const baseUrl = process.env.OLLAMA_BASE_URL;
|
||||
if (!baseUrl) throw new Error("OLLAMA_BASE_URL is not set");
|
||||
|
||||
// Explicit null signals "freeform text" — no JSON-schema constraint.
|
||||
const useFreeformText = outputSchema === null;
|
||||
|
||||
let apiUsed = null;
|
||||
let chatSupported = false;
|
||||
let rawResponse = null;
|
||||
@@ -382,15 +385,19 @@ class OllamaLlmProvider {
|
||||
try {
|
||||
apiUsed = "/api/chat";
|
||||
providerExecution.chatRequestAttempted = true;
|
||||
const body = {
|
||||
model: modelName,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
};
|
||||
// Explicit null → omit format constraint entirely.
|
||||
if (outputSchema !== null) {
|
||||
body.format = outputSchema ?? reconstructionJsonSchema;
|
||||
}
|
||||
const res = await fetch(`${baseUrl}/api/chat`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
model: modelName,
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
stream: false,
|
||||
format: outputSchema ?? reconstructionJsonSchema,
|
||||
}),
|
||||
body: JSON.stringify(body),
|
||||
signal: requestAbort.signal,
|
||||
});
|
||||
|
||||
@@ -445,6 +452,11 @@ class OllamaLlmProvider {
|
||||
? fullResponseData.response
|
||||
: JSON.stringify(fullResponseData);
|
||||
|
||||
// generate has no format constraint — treat freeform input as freeform output.
|
||||
if (useFreeformText) {
|
||||
apiUsed = "/api/generate";
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (apiUsed === "/api/generate") {
|
||||
const error = new Error(
|
||||
@@ -476,11 +488,11 @@ class OllamaLlmProvider {
|
||||
// ================================================================
|
||||
if (rawResponse === "") {
|
||||
let diagInfo = "";
|
||||
|
||||
|
||||
if (fullResponseData) {
|
||||
const keys = Object.keys(fullResponseData);
|
||||
diagInfo = "Keys in response: " + keys.join(", ") + "\n";
|
||||
|
||||
|
||||
for (const key of keys) {
|
||||
const val = fullResponseData[key];
|
||||
if (typeof val === "string") {
|
||||
@@ -501,6 +513,7 @@ class OllamaLlmProvider {
|
||||
"\nPossible causes:\n" +
|
||||
"- Check 'ollama list' — make sure the model name matches exactly what's installed\n" +
|
||||
"- The model may be corrupted. Try: ollama pull " + modelName + "\n" +
|
||||
(useFreeformText ? "- No format constraint was sent — the model may not support freeform output.\n" : "") +
|
||||
"- This Ollama version does not support format:json — using prompt instructions only (reliability varies)\n" +
|
||||
"- If your model is very small (e.g., tinyllama, phi), try a larger one like llama3.1 or mistral"
|
||||
);
|
||||
@@ -512,6 +525,14 @@ class OllamaLlmProvider {
|
||||
// ================================================================
|
||||
// Step 5: Parse and return
|
||||
// ================================================================
|
||||
if (useFreeformText) {
|
||||
return {
|
||||
response: rawResponse,
|
||||
providerApiPath: apiUsed,
|
||||
providerExecution,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
response: recoverJson(rawResponse),
|
||||
@@ -553,17 +574,27 @@ class OpenAIReconstructionProvider {
|
||||
{ signal, outputZodSchema } = {},
|
||||
) {
|
||||
if (!this.apiKey) throw new Error("OPENAI_API_KEY is not set");
|
||||
const structuredOutputSchema = outputSchema
|
||||
? createOpenAIStrictSchema(outputSchema, outputZodSchema ?? null)
|
||||
: openAIReconstructionJsonSchema;
|
||||
|
||||
const format = {
|
||||
type: "json_schema",
|
||||
name: "reconstruction",
|
||||
strict: true,
|
||||
schema: structuredOutputSchema,
|
||||
};
|
||||
traceOpenAISchema({ model: modelName, format, schema: structuredOutputSchema });
|
||||
// Explicit null → freeform text output (no JSON-schema constraint).
|
||||
const useFreeformText = outputSchema === null;
|
||||
|
||||
let format;
|
||||
if (useFreeformText) {
|
||||
format = { type: "text" };
|
||||
} else {
|
||||
const structuredOutputSchema = outputSchema
|
||||
? createOpenAIStrictSchema(outputSchema, outputZodSchema ?? null)
|
||||
: openAIReconstructionJsonSchema;
|
||||
format = {
|
||||
type: "json_schema",
|
||||
name: "reconstruction",
|
||||
strict: true,
|
||||
schema: structuredOutputSchema,
|
||||
};
|
||||
|
||||
traceOpenAISchema({ model: modelName, format, schema: structuredOutputSchema });
|
||||
}
|
||||
|
||||
const response = await this.fetchImpl("https://api.openai.com/v1/responses", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -599,6 +630,14 @@ class OpenAIReconstructionProvider {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Freeform mode — return raw text without JSON recovery or transformation.
|
||||
if (useFreeformText) {
|
||||
return {
|
||||
response: outputText,
|
||||
providerApiPath: "/v1/responses",
|
||||
};
|
||||
}
|
||||
|
||||
const recoveredResponse = recoverJson(outputText);
|
||||
const normalisedResponse = outputSchema && !outputZodSchema
|
||||
? recoveredResponse
|
||||
|
||||
Reference in New Issue
Block a user