self-evolve-framework 1.5.0 → 1.6.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.
Files changed (149) hide show
  1. package/package.json +1 -1
  2. package/template/skills/skillopt-sleep/SKILL.md +42 -0
  3. package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
  4. package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
  5. package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
  6. package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
  7. package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
  8. package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
  9. package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
  10. package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
  11. package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
  12. package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
  13. package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
  14. package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
  15. package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
  16. package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
  17. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
  18. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
  19. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
  20. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
  21. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
  22. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
  23. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
  24. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
  25. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
  26. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
  27. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
  28. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
  29. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
  30. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
  31. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
  32. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
  33. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
  34. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
  35. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
  36. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
  37. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
  38. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
  39. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
  40. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
  41. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
  42. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
  43. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
  44. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
  45. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
  46. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
  47. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
  48. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
  49. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
  50. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
  51. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
  52. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
  53. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
  54. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
  55. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
  56. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
  57. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
  58. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
  59. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
  60. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
  61. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
  62. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
  63. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
  64. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
  65. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
  66. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
  67. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
  68. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
  69. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
  70. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
  71. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
  72. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
  73. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
  74. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
  75. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
  76. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
  77. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
  78. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
  79. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
  80. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
  81. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
  82. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
  83. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
  84. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
  85. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
  86. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
  87. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
  88. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
  89. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
  90. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
  91. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
  92. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
  93. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
  94. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
  95. package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
  96. package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
  97. package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
  98. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
  99. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
  100. package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
  101. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
  102. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
  103. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
  104. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
  105. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
  106. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
  107. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
  108. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
  109. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
  110. package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
  111. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
  112. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
  113. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
  114. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
  115. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
  116. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
  117. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
  118. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
  119. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
  120. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
  121. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
  122. package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
  123. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
  124. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
  125. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
  126. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
  127. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
  128. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
  129. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
  130. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
  131. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
  132. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
  133. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
  134. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
  135. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
  136. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
  137. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
  138. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
  139. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
  140. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
  141. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
  142. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
  143. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
  144. package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
  145. package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
  146. package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
  147. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
  148. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
  149. package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/scoring.py +28 -0
@@ -0,0 +1,229 @@
1
+ """Shared model utilities for ReflACT backends."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import threading
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
8
+
9
+
10
+ _RESPONSES_API_MODELS = {
11
+ "gpt-5.3-codex",
12
+ "gpt-5.1-codex",
13
+ "gpt-5.2-codex",
14
+ "gpt-5-codex",
15
+ "codex-mini",
16
+ "gpt-5.4-pro",
17
+ }
18
+
19
+ _BACKEND_DEFAULT_MODELS = {
20
+ "azure_openai": "gpt-4o",
21
+ "openai_chat": "gpt-4o",
22
+ "codex": "gpt-4o",
23
+ "codex_exec": "gpt-4o",
24
+ "claude": "claude-sonnet-4-6",
25
+ "claude_chat": "claude-sonnet-4-6",
26
+ "claude_code_exec": "claude-sonnet-4-6",
27
+ "qwen_chat": "Qwen/Qwen3.5-4B",
28
+ "minimax_chat": "MiniMax-M2.7",
29
+ }
30
+
31
+ _BACKEND_ALIASES = {
32
+ "azure": "azure_openai",
33
+ "azure_openai": "azure_openai",
34
+ "azure-openai": "azure_openai",
35
+ "openai_chat": "openai_chat",
36
+ "openai": "codex",
37
+ "codex": "codex",
38
+ "codex_exec": "codex_exec",
39
+ "claude": "claude_chat",
40
+ "claude_chat": "claude_chat",
41
+ "claude_code_exec": "claude_code_exec",
42
+ "anthropic": "claude_chat",
43
+ "qwen": "qwen_chat",
44
+ "qwen_chat": "qwen_chat",
45
+ "minimax": "minimax_chat",
46
+ "minimax_chat": "minimax_chat",
47
+ }
48
+
49
+
50
+ def normalize_backend_name(name: str | None) -> str:
51
+ normalized = str(name or "").strip().lower()
52
+ return _BACKEND_ALIASES.get(normalized, normalized or "azure_openai")
53
+
54
+
55
+ def default_model_for_backend(backend: str | None) -> str:
56
+ return _BACKEND_DEFAULT_MODELS.get(
57
+ normalize_backend_name(backend),
58
+ _BACKEND_DEFAULT_MODELS["azure_openai"],
59
+ )
60
+
61
+
62
+ def needs_responses_api(model: str) -> bool:
63
+ normalized = str(model or "").strip().lower()
64
+ return any(
65
+ normalized == prefix or normalized.startswith(prefix + "-")
66
+ for prefix in _RESPONSES_API_MODELS
67
+ )
68
+
69
+
70
+ class TokenTracker:
71
+ def __init__(self) -> None:
72
+ self._lock = threading.Lock()
73
+ self._data: dict[str, dict[str, int]] = {}
74
+
75
+ def record(self, stage: str, prompt_tokens: int, completion_tokens: int) -> None:
76
+ with self._lock:
77
+ if stage not in self._data:
78
+ self._data[stage] = {
79
+ "calls": 0,
80
+ "prompt_tokens": 0,
81
+ "completion_tokens": 0,
82
+ }
83
+ entry = self._data[stage]
84
+ entry["calls"] += 1
85
+ entry["prompt_tokens"] += prompt_tokens
86
+ entry["completion_tokens"] += completion_tokens
87
+
88
+ def summary(self) -> dict[str, dict[str, int]]:
89
+ with self._lock:
90
+ out: dict[str, dict[str, int]] = {}
91
+ total_prompt = total_completion = total_calls = 0
92
+ for stage, entry in sorted(self._data.items()):
93
+ prompt_tokens = entry["prompt_tokens"]
94
+ completion_tokens = entry["completion_tokens"]
95
+ out[stage] = {
96
+ "calls": entry["calls"],
97
+ "prompt_tokens": prompt_tokens,
98
+ "completion_tokens": completion_tokens,
99
+ "total_tokens": prompt_tokens + completion_tokens,
100
+ }
101
+ total_prompt += prompt_tokens
102
+ total_completion += completion_tokens
103
+ total_calls += entry["calls"]
104
+ out["_total"] = {
105
+ "calls": total_calls,
106
+ "prompt_tokens": total_prompt,
107
+ "completion_tokens": total_completion,
108
+ "total_tokens": total_prompt + total_completion,
109
+ }
110
+ return out
111
+
112
+ def reset(self) -> None:
113
+ with self._lock:
114
+ self._data.clear()
115
+
116
+
117
+ tracker = TokenTracker()
118
+
119
+
120
+ @dataclass
121
+ class CompatToolFunction:
122
+ name: str
123
+ arguments: str
124
+
125
+ def model_dump(self, mode: str = "json") -> dict[str, str]:
126
+ del mode
127
+ return {
128
+ "name": self.name,
129
+ "arguments": self.arguments,
130
+ }
131
+
132
+
133
+ @dataclass
134
+ class CompatToolCall:
135
+ id: str
136
+ function: CompatToolFunction
137
+ type: str = "function"
138
+
139
+ def model_dump(self, mode: str = "json") -> dict[str, Any]:
140
+ del mode
141
+ return {
142
+ "id": self.id,
143
+ "type": self.type,
144
+ "function": self.function.model_dump(),
145
+ }
146
+
147
+
148
+ @dataclass
149
+ class CompatAssistantMessage:
150
+ content: str
151
+ tool_calls: list[CompatToolCall] = field(default_factory=list)
152
+ metadata: dict[str, Any] = field(default_factory=dict)
153
+
154
+ def model_dump(self, mode: str = "json") -> dict[str, Any]:
155
+ del mode
156
+ data: dict[str, Any] = {"role": "assistant", "content": self.content}
157
+ if self.tool_calls:
158
+ data["tool_calls"] = [tool_call.model_dump() for tool_call in self.tool_calls]
159
+ return data
160
+
161
+
162
+ def usage_from_openai_usage(usage: Any) -> dict[str, int]:
163
+ if not usage:
164
+ return {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
165
+ prompt_tokens = getattr(usage, "prompt_tokens", 0) or 0
166
+ completion_tokens = getattr(usage, "completion_tokens", 0) or 0
167
+ total_tokens = getattr(usage, "total_tokens", 0) or (prompt_tokens + completion_tokens)
168
+ return {
169
+ "prompt_tokens": prompt_tokens,
170
+ "completion_tokens": completion_tokens,
171
+ "total_tokens": total_tokens,
172
+ }
173
+
174
+
175
+ def usage_from_responses_usage(usage: Any) -> dict[str, int]:
176
+ if not usage:
177
+ return {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
178
+ prompt_tokens = getattr(usage, "input_tokens", 0) or 0
179
+ completion_tokens = getattr(usage, "output_tokens", 0) or 0
180
+ return {
181
+ "prompt_tokens": prompt_tokens,
182
+ "completion_tokens": completion_tokens,
183
+ "total_tokens": prompt_tokens + completion_tokens,
184
+ }
185
+
186
+
187
+ def compat_message_from_chat_message(message: Any) -> CompatAssistantMessage:
188
+ content = getattr(message, "content", "") or ""
189
+ tool_calls = []
190
+ for tool_call in getattr(message, "tool_calls", None) or []:
191
+ function = getattr(tool_call, "function", None)
192
+ tool_calls.append(
193
+ CompatToolCall(
194
+ id=getattr(tool_call, "id", "") or "",
195
+ function=CompatToolFunction(
196
+ name=getattr(function, "name", "") or "",
197
+ arguments=getattr(function, "arguments", "") or "{}",
198
+ ),
199
+ )
200
+ )
201
+ return CompatAssistantMessage(content=content, tool_calls=tool_calls)
202
+
203
+
204
+ def compat_message_from_responses_output(output: list[Any]) -> CompatAssistantMessage:
205
+ text_parts: list[str] = []
206
+ tool_calls: list[CompatToolCall] = []
207
+ for item in output:
208
+ item_type = getattr(item, "type", "") or ""
209
+ if item_type == "function_call":
210
+ raw_arguments = getattr(item, "arguments", None)
211
+ if raw_arguments is None:
212
+ raw_arguments = json.dumps(getattr(item, "input", {}) or {})
213
+ tool_calls.append(
214
+ CompatToolCall(
215
+ id=getattr(item, "call_id", "") or getattr(item, "id", "") or "",
216
+ function=CompatToolFunction(
217
+ name=getattr(item, "name", "") or "",
218
+ arguments=str(raw_arguments or "{}"),
219
+ ),
220
+ )
221
+ )
222
+ continue
223
+ if item_type != "message":
224
+ continue
225
+ for part in getattr(item, "content", []) or []:
226
+ part_type = getattr(part, "type", "") or ""
227
+ if part_type in {"output_text", "text"}:
228
+ text_parts.append(getattr(part, "text", "") or "")
229
+ return CompatAssistantMessage(content="".join(text_parts), tool_calls=tool_calls)
@@ -0,0 +1,277 @@
1
+ """OpenAI-compatible MiniMax chat backend for the target path."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import threading
7
+ import time
8
+ import urllib.error
9
+ import urllib.request
10
+ from typing import Any
11
+
12
+ from skillopt.model.common import (
13
+ CompatAssistantMessage,
14
+ CompatToolCall,
15
+ CompatToolFunction,
16
+ TokenTracker,
17
+ default_model_for_backend,
18
+ )
19
+
20
+ BASE_URL = os.environ.get("MINIMAX_BASE_URL", "https://api.minimax.io/v1")
21
+ API_KEY = os.environ.get("MINIMAX_API_KEY", "")
22
+ TIMEOUT_SECONDS = float(os.environ.get("MINIMAX_TIMEOUT_SECONDS", "300") or 300)
23
+ MAX_TOKENS = int(os.environ.get("MINIMAX_MAX_TOKENS", "8000") or 8000)
24
+ TEMPERATURE: float | None = None
25
+ _raw_temperature = os.environ.get("MINIMAX_TEMPERATURE", "0.7").strip()
26
+ if _raw_temperature:
27
+ TEMPERATURE = float(_raw_temperature)
28
+ ENABLE_THINKING = os.environ.get("MINIMAX_ENABLE_THINKING", "false").strip().lower() in {
29
+ "1",
30
+ "true",
31
+ "yes",
32
+ "on",
33
+ }
34
+
35
+ TARGET_DEPLOYMENT = os.environ.get(
36
+ "TARGET_DEPLOYMENT",
37
+ default_model_for_backend("minimax_chat"),
38
+ )
39
+
40
+ _config_lock = threading.Lock()
41
+ tracker = TokenTracker()
42
+
43
+
44
+ def _chat_url() -> str:
45
+ base = BASE_URL.rstrip("/")
46
+ if base.endswith("/chat/completions"):
47
+ return base
48
+ return f"{base}/chat/completions"
49
+
50
+
51
+ def _json_safe(value: Any) -> Any:
52
+ if value is None or isinstance(value, (str, int, float, bool)):
53
+ return value
54
+ if isinstance(value, list):
55
+ return [_json_safe(item) for item in value]
56
+ if isinstance(value, dict):
57
+ return {str(key): _json_safe(val) for key, val in value.items()}
58
+ model_dump = getattr(value, "model_dump", None)
59
+ if callable(model_dump):
60
+ try:
61
+ return model_dump(mode="json")
62
+ except TypeError:
63
+ return model_dump()
64
+ return str(value)
65
+
66
+
67
+ def _usage_from_payload(payload: dict[str, Any]) -> dict[str, int]:
68
+ usage = payload.get("usage") or {}
69
+ prompt_tokens = int(usage.get("prompt_tokens") or usage.get("input_tokens") or 0)
70
+ completion_tokens = int(usage.get("completion_tokens") or usage.get("output_tokens") or 0)
71
+ total_tokens = int(usage.get("total_tokens") or (prompt_tokens + completion_tokens))
72
+ return {
73
+ "prompt_tokens": prompt_tokens,
74
+ "completion_tokens": completion_tokens,
75
+ "total_tokens": total_tokens,
76
+ }
77
+
78
+
79
+ def _compat_message_from_payload(message: dict[str, Any], choice: dict[str, Any]) -> CompatAssistantMessage:
80
+ content = message.get("content") or ""
81
+ if not isinstance(content, str):
82
+ content = json.dumps(content, ensure_ascii=False)
83
+ tool_calls: list[CompatToolCall] = []
84
+ for index, tool_call in enumerate(message.get("tool_calls") or [], start=1):
85
+ function = tool_call.get("function") or {}
86
+ tool_calls.append(
87
+ CompatToolCall(
88
+ id=str(tool_call.get("id") or f"minimax_tool_{index}"),
89
+ type=str(tool_call.get("type") or "function"),
90
+ function=CompatToolFunction(
91
+ name=str(function.get("name") or ""),
92
+ arguments=str(function.get("arguments") or "{}"),
93
+ ),
94
+ )
95
+ )
96
+ return CompatAssistantMessage(
97
+ content=content,
98
+ tool_calls=tool_calls,
99
+ metadata={
100
+ "finish_reason": choice.get("finish_reason"),
101
+ "choice0": _json_safe(choice),
102
+ },
103
+ )
104
+
105
+
106
+ def _post_chat_completion(payload: dict[str, Any], timeout: float | None) -> dict[str, Any]:
107
+ headers = {"Content-Type": "application/json"}
108
+ if API_KEY:
109
+ headers["Authorization"] = f"Bearer {API_KEY}"
110
+ req = urllib.request.Request(
111
+ _chat_url(),
112
+ data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
113
+ headers=headers,
114
+ method="POST",
115
+ )
116
+ try:
117
+ with urllib.request.urlopen(req, timeout=timeout or TIMEOUT_SECONDS) as resp:
118
+ raw = resp.read().decode("utf-8")
119
+ except urllib.error.HTTPError as e:
120
+ body = e.read().decode("utf-8", errors="replace")
121
+ raise RuntimeError(f"MiniMax chat API returned HTTP {e.code}: {body}") from e
122
+ except urllib.error.URLError as e:
123
+ raise RuntimeError(f"MiniMax chat API request failed: {e}") from e
124
+ try:
125
+ return json.loads(raw)
126
+ except json.JSONDecodeError as e:
127
+ raise RuntimeError(f"MiniMax chat API returned non-JSON response: {raw[:1000]}") from e
128
+
129
+
130
+ def _chat_messages_impl(
131
+ messages: list[dict[str, Any]],
132
+ max_completion_tokens: int,
133
+ retries: int,
134
+ stage: str,
135
+ *,
136
+ tools: list[dict[str, Any]] | None = None,
137
+ tool_choice: str | dict[str, Any] | None = None,
138
+ return_message: bool = False,
139
+ deployment: str | None = None,
140
+ timeout: float | None = None,
141
+ ) -> tuple[Any, dict[str, int]]:
142
+ payload: dict[str, Any] = {
143
+ "model": deployment or TARGET_DEPLOYMENT,
144
+ "messages": _json_safe(messages),
145
+ "max_tokens": min(max_completion_tokens, MAX_TOKENS),
146
+ }
147
+ payload["chat_template_kwargs"] = {"enable_thinking": ENABLE_THINKING}
148
+ if TEMPERATURE is not None:
149
+ payload["temperature"] = TEMPERATURE
150
+ if tools:
151
+ payload["tools"] = _json_safe(tools)
152
+ if tool_choice is not None:
153
+ payload["tool_choice"] = _json_safe(tool_choice)
154
+
155
+ last_err: Exception | None = None
156
+ for attempt in range(retries):
157
+ try:
158
+ data = _post_chat_completion(payload, timeout)
159
+ choices = data.get("choices") or []
160
+ if not choices:
161
+ raise RuntimeError(f"MiniMax chat API returned no choices: {data}")
162
+ choice0 = choices[0]
163
+ message = choice0.get("message") or {}
164
+ text = message.get("content") or ""
165
+ if not isinstance(text, str):
166
+ text = json.dumps(text, ensure_ascii=False)
167
+ usage_info = _usage_from_payload(data)
168
+ tracker.record(stage, usage_info["prompt_tokens"], usage_info["completion_tokens"])
169
+ if return_message:
170
+ return _compat_message_from_payload(message, choice0), usage_info
171
+ return text, usage_info
172
+ except Exception as e: # noqa: BLE001
173
+ last_err = e
174
+ time.sleep(min(2 ** attempt, 30))
175
+ raise RuntimeError(f"MiniMax chat call failed after {retries} retries: {last_err}")
176
+
177
+
178
+ def configure_minimax_chat(
179
+ *,
180
+ base_url: str | None = None,
181
+ api_key: str | None = None,
182
+ temperature: float | str | None = None,
183
+ timeout_seconds: float | str | None = None,
184
+ max_tokens: int | str | None = None,
185
+ enable_thinking: bool | str | None = None,
186
+ ) -> None:
187
+ global BASE_URL, API_KEY, TEMPERATURE, TIMEOUT_SECONDS, MAX_TOKENS, ENABLE_THINKING
188
+ with _config_lock:
189
+ if base_url is not None:
190
+ BASE_URL = str(base_url).strip() or BASE_URL
191
+ os.environ["MINIMAX_BASE_URL"] = BASE_URL
192
+ if api_key is not None:
193
+ API_KEY = str(api_key).strip()
194
+ os.environ["MINIMAX_API_KEY"] = API_KEY
195
+ if temperature is not None:
196
+ raw = str(temperature).strip()
197
+ TEMPERATURE = float(raw) if raw else None
198
+ os.environ["MINIMAX_TEMPERATURE"] = raw
199
+ if timeout_seconds is not None:
200
+ TIMEOUT_SECONDS = float(timeout_seconds)
201
+ os.environ["MINIMAX_TIMEOUT_SECONDS"] = str(timeout_seconds)
202
+ if max_tokens is not None:
203
+ MAX_TOKENS = int(max_tokens)
204
+ os.environ["MINIMAX_MAX_TOKENS"] = str(max_tokens)
205
+ if enable_thinking is not None:
206
+ if isinstance(enable_thinking, str):
207
+ ENABLE_THINKING = enable_thinking.strip().lower() in {"1", "true", "yes", "on"}
208
+ else:
209
+ ENABLE_THINKING = bool(enable_thinking)
210
+ os.environ["MINIMAX_ENABLE_THINKING"] = "true" if ENABLE_THINKING else "false"
211
+
212
+
213
+ def get_max_tokens() -> int:
214
+ return MAX_TOKENS
215
+
216
+
217
+ def chat_target(
218
+ system: str,
219
+ user: str,
220
+ max_completion_tokens: int = 16384,
221
+ retries: int = 5,
222
+ stage: str = "target",
223
+ reasoning_effort: str | None = None,
224
+ timeout: float | None = None,
225
+ ) -> tuple[str, dict[str, int]]:
226
+ del reasoning_effort
227
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
228
+ return _chat_messages_impl(
229
+ messages,
230
+ max_completion_tokens,
231
+ retries,
232
+ stage,
233
+ timeout=timeout,
234
+ )
235
+
236
+
237
+ def chat_target_messages(
238
+ messages: list[dict[str, Any]],
239
+ max_completion_tokens: int = 16384,
240
+ retries: int = 5,
241
+ stage: str = "target",
242
+ reasoning_effort: str | None = None,
243
+ *,
244
+ tools: list[dict[str, Any]] | None = None,
245
+ tool_choice: str | dict[str, Any] | None = None,
246
+ return_message: bool = False,
247
+ timeout: float | None = None,
248
+ ) -> tuple[Any, dict[str, int]]:
249
+ del reasoning_effort
250
+ return _chat_messages_impl(
251
+ messages,
252
+ max_completion_tokens,
253
+ retries,
254
+ stage,
255
+ tools=tools,
256
+ tool_choice=tool_choice,
257
+ return_message=return_message,
258
+ timeout=timeout,
259
+ )
260
+
261
+
262
+ def get_token_summary() -> dict[str, dict[str, int]]:
263
+ return tracker.summary()
264
+
265
+
266
+ def reset_token_tracker() -> None:
267
+ tracker.reset()
268
+
269
+
270
+ def set_reasoning_effort(effort: str | None) -> None:
271
+ del effort
272
+
273
+
274
+ def set_target_deployment(deployment: str) -> None:
275
+ global TARGET_DEPLOYMENT
276
+ TARGET_DEPLOYMENT = deployment or default_model_for_backend("minimax_chat")
277
+ os.environ["TARGET_DEPLOYMENT"] = TARGET_DEPLOYMENT