mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
Reasoning models on Ollama and similar servers think by default and can spend the whole max_tokens budget before the visible answer starts, so the call comes back empty or times out. The Local and OpenAI-compatible chain entries now carry an optional reasoning setting: Default sends nothing, Off sends reasoning_effort "none", Low / Medium / High send their own value. It is stored on the entry in aiProviderChain, so a chain can mix a thinking model with one that does not know the field. - Every exported resolver binds the entry's value onto the client, the connection test included, keyed on the instance type so the operator provider, OpenAI, Anthropic and Codex never carry it. - PATCH /api/user/ai-provider accepts localReasoningEffort and compatReasoningEffort and GET reports them. A provider absent from the chain holds its value on a disabled entry; restating Default on a chain that was never customised writes nothing. - PUT /api/insights/provider-chain accepts reasoningEffort on local and openai-compatible entries, refuses it on any other, and keeps the stored value when the body leaves it out. Its schema is now shared with the OpenAPI registry. - A Default request body is byte-identical to the one sent before. Refs #1126
84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
/**
|
|
* #1126 — the per-entry reasoning setting of the Local and OpenAI-compatible
|
|
* providers, and the binder that stamps it onto a resolved provider.
|
|
*
|
|
* A leaf on purpose: `types.ts` and the binder are reachable from workers that
|
|
* must not reach the provider machinery (`data-arrival-provider-isolation`),
|
|
* so nothing here imports `provider-chain.ts`. The stored entry is read from
|
|
* the raw chain directly, first match wins, the same rule
|
|
* `parseProviderChain` dedupes by.
|
|
*/
|
|
import type { AIProvider } from "./types";
|
|
|
|
/**
|
|
* The `reasoning_effort` values a chain entry may carry. "Off" in the settings
|
|
* is `none` on the wire. The absence of the key is "Default": nothing is sent
|
|
* and the model decides, which is what every chain stored before the setting
|
|
* existed keeps doing.
|
|
*/
|
|
export const REASONING_EFFORTS = ["none", "low", "medium", "high"] as const;
|
|
|
|
export type ReasoningEffort = (typeof REASONING_EFFORTS)[number];
|
|
|
|
/**
|
|
* The entries that may carry a reasoning effort: the two whose wire is a
|
|
* person-chosen `/chat/completions` endpoint serving a model of their choice.
|
|
* OpenAI, Anthropic and Codex pin their own model families and parameters,
|
|
* so the setting is refused for them rather than silently dropped.
|
|
*/
|
|
export const REASONING_PROVIDER_TYPES = ["local", "openai-compatible"] as const;
|
|
|
|
export type ReasoningProviderType = (typeof REASONING_PROVIDER_TYPES)[number];
|
|
|
|
export function isReasoningProviderType(
|
|
providerType: string,
|
|
): providerType is ReasoningProviderType {
|
|
return (REASONING_PROVIDER_TYPES as readonly string[]).includes(providerType);
|
|
}
|
|
|
|
export function isReasoningEffort(value: unknown): value is ReasoningEffort {
|
|
return (
|
|
typeof value === "string" &&
|
|
(REASONING_EFFORTS as readonly string[]).includes(value)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* The reasoning effort stored on a provider type's entry in a persisted chain,
|
|
* enabled or not. `null` is Default: no chain, no entry, no valid value, or a
|
|
* type that cannot carry one.
|
|
*/
|
|
export function reasoningEffortFor(
|
|
rawChain: unknown,
|
|
providerType: string,
|
|
): ReasoningEffort | null {
|
|
if (!isReasoningProviderType(providerType) || !Array.isArray(rawChain)) {
|
|
return null;
|
|
}
|
|
const entry = rawChain.find(
|
|
(item): item is { reasoningEffort?: unknown } =>
|
|
item != null &&
|
|
typeof item === "object" &&
|
|
(item as { providerType?: unknown }).providerType === providerType,
|
|
);
|
|
return isReasoningEffort(entry?.reasoningEffort)
|
|
? entry.reasoningEffort
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Stamp the record owner's reasoning effort onto a resolved provider. Keyed on
|
|
* the instance's runtime type, so only a Local client or an OpenAI-compatible
|
|
* gateway can ever carry one: the operator's provider, the personal OpenAI
|
|
* key, Anthropic and Codex resolve to `null` whatever the stored chain says.
|
|
* Called by every exported resolver in `provider.ts` next to
|
|
* `bindResponseTimeout`, with the raw `User.aiProviderChain` value.
|
|
*/
|
|
export function bindReasoningEffort<P extends AIProvider>(
|
|
provider: P,
|
|
aiProviderChain: unknown,
|
|
): P {
|
|
provider.reasoningEffort = reasoningEffortFor(aiProviderChain, provider.type);
|
|
return provider;
|
|
}
|