@ai-for-dev/providers 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,10 +31,13 @@ Vous pouvez aussi fournir la clé par variable d'environnement : `ILAAS_API_KEY`
31
31
 
32
32
  `extensions/index.ts` contient, pour chaque serveur, une table des modèles connus : taille du contexte, sortie maximale, lecture d'images et gestion du raisonnement. Ces valeurs ont été mesurées sur les serveurs. Le raisonnement peut être :
33
33
 
34
- - activable (`toggle`) : Pi propose `off` et `high`, et passe par `chat_template_kwargs.enable_thinking` ;
34
+ - réglable par niveau : Pi propose `off`, `low`, `medium` et `high`, et envoie `enable_thinking` et `reasoning_effort` dans `chat_template_kwargs`. La table donne la valeur à envoyer pour chaque niveau, car chaque modèle a son vocabulaire (`xhigh` pour Qwen 3.8, `max` pour DeepSeek V4.1) ;
35
+ - activable (`toggle`) : le modèle ne sait que réfléchir ou non. Pi propose `off` et `high`, et passe par `chat_template_kwargs.enable_thinking` ;
35
36
  - permanent (`always`) : Pi ne propose que `high` ;
36
37
  - absent (`none`) : Pi n'envoie aucun paramètre de raisonnement.
37
38
 
39
+ Seuls `qwen-3.8-27b` et `ARES/DeepSeek-V4.1-Flash` lisent un niveau d'effort dans leur modèle de chat. Ceux de Qwen 3.6 et de Gemma 4 ne connaissent que `enable_thinking`.
40
+
38
41
  Un modèle marqué `hidden: true` n'apparaît pas, même si le serveur le liste. Un modèle qui n'est pas dans la table reste utilisable. Il prend la taille de contexte donnée par le serveur (128 000 tokens à défaut), 32 768 tokens de sortie, sans images ni raisonnement.
39
42
 
40
43
  ## Limites
@@ -13,11 +13,19 @@ type ChatModel = Model<"openai-completions">;
13
13
 
14
14
  /**
15
15
  * How a model handles reasoning, measured against the real servers:
16
- * - "toggle": off by default, switched on with chat_template_kwargs.enable_thinking (vLLM).
16
+ * - "toggle": chat_template_kwargs.enable_thinking switches reasoning on and off, nothing finer.
17
17
  * - "always": always reasons and ignores every switch.
18
18
  * - "none": no reasoning; any reasoning parameter makes the server fail.
19
+ * - Efforts: the chat template also reads a reasoning_effort, given here as the value
20
+ * to send for each of Pi's low, medium and high levels.
19
21
  */
20
- type Thinking = "toggle" | "always" | "none";
22
+ type Thinking = "toggle" | "always" | "none" | Efforts;
23
+
24
+ interface Efforts {
25
+ low: string;
26
+ medium: string;
27
+ high: string;
28
+ }
21
29
 
22
30
  interface ModelTuning {
23
31
  contextWindow?: number;
@@ -61,8 +69,10 @@ const ENDPOINTS: Endpoint[] = [
61
69
  baseUrl: "https://ia4dev-owui.univ-grenoble-alpes.fr/api",
62
70
  envVar: "IA4DEV_API_KEY",
63
71
  models: {
64
- "qwen-3.8-27b": { vision: true, thinking: "toggle" },
65
- "ARES/DeepSeek-V4.1-Flash": { vision: true, thinking: "toggle" },
72
+ // The chat template accepts low, medium and xhigh (its default).
73
+ "qwen-3.8-27b": { vision: true, thinking: { low: "low", medium: "medium", high: "xhigh" } },
74
+ // The deployed chat template accepts low, high (its default) and max, but not medium.
75
+ "ARES/DeepSeek-V4.1-Flash": { vision: true, thinking: { low: "low", medium: "high", high: "max" } },
66
76
  "gemma-4-31b": { vision: true, thinking: "toggle" },
67
77
  },
68
78
  },
@@ -72,11 +82,34 @@ const DEFAULT_CONTEXT_WINDOW = 128_000;
72
82
  const DEFAULT_MAX_TOKENS = 32_768;
73
83
  const FETCH_TIMEOUT_MS = 10_000;
74
84
 
75
- // Effort levels mean nothing to these servers: reasoning is either on or off.
76
- const THINKING_LEVELS: Record<Exclude<Thinking, "none">, ChatModel["thinkingLevelMap"]> = {
77
- toggle: { minimal: null, low: null, medium: null },
78
- always: { off: null, minimal: null, low: null, medium: null },
79
- };
85
+ type Reasoning = Pick<ChatModel, "reasoning" | "thinkingLevelMap"> & { compat: ChatModel["compat"] };
86
+
87
+ // Pi's levels without a counterpart on the server are mapped to null, which hides them.
88
+ function reasoningFor(thinking: Thinking): Reasoning {
89
+ if (thinking === "none") return { reasoning: false, compat: {} };
90
+ if (thinking === "always") {
91
+ return { reasoning: true, thinkingLevelMap: { off: null, minimal: null, low: null, medium: null }, compat: {} };
92
+ }
93
+ if (thinking === "toggle") {
94
+ return {
95
+ reasoning: true,
96
+ thinkingLevelMap: { minimal: null, low: null, medium: null },
97
+ compat: { thinkingFormat: "qwen-chat-template" },
98
+ };
99
+ }
100
+ return {
101
+ reasoning: true,
102
+ thinkingLevelMap: { minimal: null, ...thinking },
103
+ compat: {
104
+ thinkingFormat: "chat-template",
105
+ chatTemplateKwargs: {
106
+ enable_thinking: { $var: "thinking.enabled" },
107
+ preserve_thinking: true,
108
+ reasoning_effort: { $var: "thinking.effort", omitWhenOff: true },
109
+ },
110
+ },
111
+ };
112
+ }
80
113
 
81
114
  interface RemoteModel {
82
115
  id: string;
@@ -90,7 +123,7 @@ interface RemoteModel {
90
123
 
91
124
  function toModel(endpoint: Endpoint, remote: RemoteModel): ChatModel {
92
125
  const tuning = endpoint.models[remote.id] ?? {};
93
- const thinking = tuning.thinking ?? "none";
126
+ const reasoning = reasoningFor(tuning.thinking ?? "none");
94
127
  const contextWindow =
95
128
  tuning.contextWindow ?? remote.max_model_len ?? remote.max_input_tokens ?? DEFAULT_CONTEXT_WINDOW;
96
129
  const maxTokens = tuning.maxTokens ?? remote.max_output_tokens ?? DEFAULT_MAX_TOKENS;
@@ -101,8 +134,8 @@ function toModel(endpoint: Endpoint, remote: RemoteModel): ChatModel {
101
134
  provider: endpoint.id,
102
135
  baseUrl: endpoint.baseUrl,
103
136
  input: tuning.vision ? ["text", "image"] : ["text"],
104
- reasoning: thinking !== "none",
105
- thinkingLevelMap: thinking === "none" ? undefined : THINKING_LEVELS[thinking],
137
+ reasoning: reasoning.reasoning,
138
+ thinkingLevelMap: reasoning.thinkingLevelMap,
106
139
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
107
140
  contextWindow,
108
141
  maxTokens: Math.min(maxTokens, contextWindow),
@@ -110,9 +143,10 @@ function toModel(endpoint: Endpoint, remote: RemoteModel): ChatModel {
110
143
  // These gateways reject the newer OpenAI-only fields.
111
144
  supportsDeveloperRole: false,
112
145
  supportsStore: false,
113
- // reasoning_effort is ignored by vLLM chat templates and crashes the Mistral models.
146
+ // A top-level reasoning_effort is ignored by vLLM chat templates and crashes the
147
+ // Mistral models; models that read an effort get it through the chat template.
114
148
  supportsReasoningEffort: false,
115
- ...(thinking === "toggle" ? { thinkingFormat: "qwen-chat-template" as const } : {}),
149
+ ...reasoning.compat,
116
150
  },
117
151
  };
118
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-for-dev/providers",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Pi providers for the AI4Dev course models (ILAAS and IA4Dev)",
5
5
  "keywords": [
6
6
  "pi",