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,456 @@
1
+ """OpenAI-compatible Qwen chat backend for optimizer and target paths."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ import json
6
+ import os
7
+ import threading
8
+ import time
9
+ import urllib.error
10
+ import urllib.request
11
+ from typing import Any
12
+
13
+ from skillopt.model.common import (
14
+ CompatAssistantMessage,
15
+ CompatToolCall,
16
+ CompatToolFunction,
17
+ TokenTracker,
18
+ default_model_for_backend,
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class QwenChatConfig:
24
+ base_url: str
25
+ api_key: str
26
+ timeout_seconds: float
27
+ max_tokens: int
28
+ temperature: float | None
29
+ enable_thinking: bool
30
+ deployment: str
31
+
32
+
33
+ def _parse_bool(value: Any, default: bool = False) -> bool:
34
+ if value is None:
35
+ return default
36
+ return str(value).strip().lower() in {"1", "true", "yes", "on"}
37
+
38
+
39
+ def _parse_optional_float(value: Any) -> float | None:
40
+ if value is None:
41
+ return None
42
+ raw = str(value).strip()
43
+ return float(raw) if raw else None
44
+
45
+
46
+ def _parse_int(value: Any, default: int) -> int:
47
+ if value is None:
48
+ return default
49
+ raw = str(value).strip()
50
+ return int(raw) if raw else default
51
+
52
+
53
+ def _role_env(role: str, key: str, default: str) -> str:
54
+ role_key = f"{role.upper()}_QWEN_CHAT_{key}"
55
+ generic_key = f"QWEN_CHAT_{key}"
56
+ return os.environ.get(role_key) or os.environ.get(generic_key) or default
57
+
58
+
59
+ def _initial_config(role: str) -> QwenChatConfig:
60
+ role_upper = role.upper()
61
+ deployment_env = "OPTIMIZER_DEPLOYMENT" if role == "optimizer" else "TARGET_DEPLOYMENT"
62
+ return QwenChatConfig(
63
+ base_url=_role_env(role, "BASE_URL", "http://localhost:8000/v1"),
64
+ api_key=_role_env(role, "API_KEY", ""),
65
+ timeout_seconds=float(_role_env(role, "TIMEOUT_SECONDS", "300") or 300),
66
+ max_tokens=_parse_int(_role_env(role, "MAX_TOKENS", "8000"), 8000),
67
+ temperature=_parse_optional_float(_role_env(role, "TEMPERATURE", "0.7")),
68
+ enable_thinking=_parse_bool(_role_env(role, "ENABLE_THINKING", "false")),
69
+ deployment=(
70
+ os.environ.get(f"{role_upper}_QWEN_CHAT_MODEL")
71
+ or os.environ.get("QWEN_CHAT_MODEL")
72
+ or os.environ.get(deployment_env)
73
+ or default_model_for_backend("qwen_chat")
74
+ ),
75
+ )
76
+
77
+
78
+ OPTIMIZER_CONFIG = _initial_config("optimizer")
79
+ TARGET_CONFIG = _initial_config("target")
80
+
81
+ _config_lock = threading.Lock()
82
+ tracker = TokenTracker()
83
+
84
+
85
+ def _chat_url(config: QwenChatConfig) -> str:
86
+ base = config.base_url.rstrip("/")
87
+ if base.endswith("/chat/completions"):
88
+ return base
89
+ return f"{base}/chat/completions"
90
+
91
+
92
+ def _json_safe(value: Any) -> Any:
93
+ if value is None or isinstance(value, (str, int, float, bool)):
94
+ return value
95
+ if isinstance(value, list):
96
+ return [_json_safe(item) for item in value]
97
+ if isinstance(value, dict):
98
+ return {str(key): _json_safe(val) for key, val in value.items()}
99
+ model_dump = getattr(value, "model_dump", None)
100
+ if callable(model_dump):
101
+ try:
102
+ return model_dump(mode="json")
103
+ except TypeError:
104
+ return model_dump()
105
+ return str(value)
106
+
107
+
108
+ def _usage_from_payload(payload: dict[str, Any]) -> dict[str, int]:
109
+ usage = payload.get("usage") or {}
110
+ prompt_tokens = int(usage.get("prompt_tokens") or usage.get("input_tokens") or 0)
111
+ completion_tokens = int(usage.get("completion_tokens") or usage.get("output_tokens") or 0)
112
+ total_tokens = int(usage.get("total_tokens") or (prompt_tokens + completion_tokens))
113
+ return {
114
+ "prompt_tokens": prompt_tokens,
115
+ "completion_tokens": completion_tokens,
116
+ "total_tokens": total_tokens,
117
+ }
118
+
119
+
120
+ def _compat_message_from_payload(message: dict[str, Any], choice: dict[str, Any]) -> CompatAssistantMessage:
121
+ content = message.get("content") or ""
122
+ if not isinstance(content, str):
123
+ content = json.dumps(content, ensure_ascii=False)
124
+ tool_calls: list[CompatToolCall] = []
125
+ for index, tool_call in enumerate(message.get("tool_calls") or [], start=1):
126
+ function = tool_call.get("function") or {}
127
+ tool_calls.append(
128
+ CompatToolCall(
129
+ id=str(tool_call.get("id") or f"qwen_tool_{index}"),
130
+ type=str(tool_call.get("type") or "function"),
131
+ function=CompatToolFunction(
132
+ name=str(function.get("name") or ""),
133
+ arguments=str(function.get("arguments") or "{}"),
134
+ ),
135
+ )
136
+ )
137
+ return CompatAssistantMessage(
138
+ content=content,
139
+ tool_calls=tool_calls,
140
+ metadata={
141
+ "finish_reason": choice.get("finish_reason"),
142
+ "choice0": _json_safe(choice),
143
+ },
144
+ )
145
+
146
+
147
+ def _post_chat_completion(
148
+ payload: dict[str, Any],
149
+ timeout: float | None,
150
+ config: QwenChatConfig,
151
+ ) -> dict[str, Any]:
152
+ headers = {"Content-Type": "application/json"}
153
+ if config.api_key:
154
+ headers["Authorization"] = f"Bearer {config.api_key}"
155
+ req = urllib.request.Request(
156
+ _chat_url(config),
157
+ data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
158
+ headers=headers,
159
+ method="POST",
160
+ )
161
+ try:
162
+ with urllib.request.urlopen(req, timeout=timeout or config.timeout_seconds) as resp:
163
+ raw = resp.read().decode("utf-8")
164
+ except urllib.error.HTTPError as e:
165
+ body = e.read().decode("utf-8", errors="replace")
166
+ raise RuntimeError(f"Qwen chat API returned HTTP {e.code}: {body}") from e
167
+ except urllib.error.URLError as e:
168
+ raise RuntimeError(f"Qwen chat API request failed: {e}") from e
169
+ try:
170
+ return json.loads(raw)
171
+ except json.JSONDecodeError as e:
172
+ raise RuntimeError(f"Qwen chat API returned non-JSON response: {raw[:1000]}") from e
173
+
174
+
175
+ def _chat_messages_impl(
176
+ messages: list[dict[str, Any]],
177
+ max_completion_tokens: int,
178
+ retries: int,
179
+ stage: str,
180
+ *,
181
+ role: str,
182
+ tools: list[dict[str, Any]] | None = None,
183
+ tool_choice: str | dict[str, Any] | None = None,
184
+ return_message: bool = False,
185
+ deployment: str | None = None,
186
+ timeout: float | None = None,
187
+ ) -> tuple[Any, dict[str, int]]:
188
+ config = OPTIMIZER_CONFIG if role == "optimizer" else TARGET_CONFIG
189
+ payload: dict[str, Any] = {
190
+ "model": deployment or config.deployment,
191
+ "messages": _json_safe(messages),
192
+ "max_tokens": min(max_completion_tokens, config.max_tokens),
193
+ }
194
+ if config.enable_thinking:
195
+ payload["chat_template_kwargs"] = {"enable_thinking": True}
196
+ if config.temperature is not None:
197
+ payload["temperature"] = config.temperature
198
+ if tools:
199
+ payload["tools"] = _json_safe(tools)
200
+ if tool_choice is not None:
201
+ payload["tool_choice"] = _json_safe(tool_choice)
202
+
203
+ last_err: Exception | None = None
204
+ for attempt in range(retries):
205
+ try:
206
+ data = _post_chat_completion(payload, timeout, config)
207
+ choices = data.get("choices") or []
208
+ if not choices:
209
+ raise RuntimeError(f"Qwen chat API returned no choices: {data}")
210
+ choice0 = choices[0]
211
+ message = choice0.get("message") or {}
212
+ text = message.get("content") or ""
213
+ if not isinstance(text, str):
214
+ text = json.dumps(text, ensure_ascii=False)
215
+ usage_info = _usage_from_payload(data)
216
+ tracker.record(stage, usage_info["prompt_tokens"], usage_info["completion_tokens"])
217
+ if return_message:
218
+ return _compat_message_from_payload(message, choice0), usage_info
219
+ return text, usage_info
220
+ except Exception as e: # noqa: BLE001
221
+ last_err = e
222
+ time.sleep(min(2 ** attempt, 30))
223
+ raise RuntimeError(f"Qwen chat call failed after {retries} retries: {last_err}")
224
+
225
+
226
+ def configure_qwen_chat(
227
+ *,
228
+ base_url: str | None = None,
229
+ api_key: str | None = None,
230
+ temperature: float | str | None = None,
231
+ timeout_seconds: float | str | None = None,
232
+ max_tokens: int | str | None = None,
233
+ enable_thinking: bool | str | None = None,
234
+ optimizer_base_url: str | None = None,
235
+ optimizer_api_key: str | None = None,
236
+ optimizer_temperature: float | str | None = None,
237
+ optimizer_timeout_seconds: float | str | None = None,
238
+ optimizer_max_tokens: int | str | None = None,
239
+ optimizer_enable_thinking: bool | str | None = None,
240
+ target_base_url: str | None = None,
241
+ target_api_key: str | None = None,
242
+ target_temperature: float | str | None = None,
243
+ target_timeout_seconds: float | str | None = None,
244
+ target_max_tokens: int | str | None = None,
245
+ target_enable_thinking: bool | str | None = None,
246
+ ) -> None:
247
+ with _config_lock:
248
+ if base_url is not None:
249
+ os.environ["QWEN_CHAT_BASE_URL"] = str(base_url).strip()
250
+ if api_key is not None:
251
+ os.environ["QWEN_CHAT_API_KEY"] = str(api_key).strip()
252
+ if temperature is not None:
253
+ os.environ["QWEN_CHAT_TEMPERATURE"] = str(temperature).strip()
254
+ if timeout_seconds is not None:
255
+ os.environ["QWEN_CHAT_TIMEOUT_SECONDS"] = str(timeout_seconds)
256
+ if max_tokens is not None:
257
+ os.environ["QWEN_CHAT_MAX_TOKENS"] = str(max_tokens)
258
+ if enable_thinking is not None:
259
+ os.environ["QWEN_CHAT_ENABLE_THINKING"] = (
260
+ "true" if _parse_bool(enable_thinking) else "false"
261
+ )
262
+ _update_config(
263
+ OPTIMIZER_CONFIG,
264
+ "optimizer",
265
+ base_url=optimizer_base_url if optimizer_base_url is not None else base_url,
266
+ api_key=optimizer_api_key if optimizer_api_key is not None else api_key,
267
+ temperature=(
268
+ optimizer_temperature
269
+ if optimizer_temperature is not None
270
+ else temperature
271
+ ),
272
+ timeout_seconds=(
273
+ optimizer_timeout_seconds
274
+ if optimizer_timeout_seconds is not None
275
+ else timeout_seconds
276
+ ),
277
+ max_tokens=optimizer_max_tokens if optimizer_max_tokens is not None else max_tokens,
278
+ enable_thinking=(
279
+ optimizer_enable_thinking
280
+ if optimizer_enable_thinking is not None
281
+ else enable_thinking
282
+ ),
283
+ )
284
+ _update_config(
285
+ TARGET_CONFIG,
286
+ "target",
287
+ base_url=target_base_url if target_base_url is not None else base_url,
288
+ api_key=target_api_key if target_api_key is not None else api_key,
289
+ temperature=target_temperature if target_temperature is not None else temperature,
290
+ timeout_seconds=(
291
+ target_timeout_seconds
292
+ if target_timeout_seconds is not None
293
+ else timeout_seconds
294
+ ),
295
+ max_tokens=target_max_tokens if target_max_tokens is not None else max_tokens,
296
+ enable_thinking=(
297
+ target_enable_thinking
298
+ if target_enable_thinking is not None
299
+ else enable_thinking
300
+ ),
301
+ )
302
+
303
+
304
+ def _update_config(
305
+ config: QwenChatConfig,
306
+ role: str,
307
+ *,
308
+ base_url: str | None = None,
309
+ api_key: str | None = None,
310
+ temperature: float | str | None = None,
311
+ timeout_seconds: float | str | None = None,
312
+ max_tokens: int | str | None = None,
313
+ enable_thinking: bool | str | None = None,
314
+ ) -> None:
315
+ env_prefix = role.upper()
316
+ if base_url is not None:
317
+ config.base_url = str(base_url).strip() or config.base_url
318
+ os.environ[f"{env_prefix}_QWEN_CHAT_BASE_URL"] = config.base_url
319
+ if api_key is not None:
320
+ config.api_key = str(api_key).strip()
321
+ os.environ[f"{env_prefix}_QWEN_CHAT_API_KEY"] = config.api_key
322
+ if temperature is not None:
323
+ raw = str(temperature).strip()
324
+ config.temperature = float(raw) if raw else None
325
+ os.environ[f"{env_prefix}_QWEN_CHAT_TEMPERATURE"] = raw
326
+ if timeout_seconds is not None:
327
+ config.timeout_seconds = float(timeout_seconds)
328
+ os.environ[f"{env_prefix}_QWEN_CHAT_TIMEOUT_SECONDS"] = str(timeout_seconds)
329
+ if max_tokens is not None:
330
+ config.max_tokens = int(max_tokens)
331
+ os.environ[f"{env_prefix}_QWEN_CHAT_MAX_TOKENS"] = str(max_tokens)
332
+ if enable_thinking is not None:
333
+ config.enable_thinking = _parse_bool(enable_thinking)
334
+ os.environ[f"{env_prefix}_QWEN_CHAT_ENABLE_THINKING"] = (
335
+ "true" if config.enable_thinking else "false"
336
+ )
337
+
338
+
339
+ def get_max_tokens() -> int:
340
+ return TARGET_CONFIG.max_tokens
341
+
342
+
343
+ def chat_optimizer(
344
+ system: str,
345
+ user: str,
346
+ max_completion_tokens: int = 16384,
347
+ retries: int = 5,
348
+ stage: str = "optimizer",
349
+ reasoning_effort: str | None = None,
350
+ timeout: float | None = None,
351
+ ) -> tuple[str, dict[str, int]]:
352
+ del reasoning_effort
353
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
354
+ return _chat_messages_impl(
355
+ messages,
356
+ max_completion_tokens,
357
+ retries,
358
+ stage,
359
+ role="optimizer",
360
+ timeout=timeout,
361
+ )
362
+
363
+
364
+ def chat_target(
365
+ system: str,
366
+ user: str,
367
+ max_completion_tokens: int = 16384,
368
+ retries: int = 5,
369
+ stage: str = "target",
370
+ reasoning_effort: str | None = None,
371
+ timeout: float | None = None,
372
+ ) -> tuple[str, dict[str, int]]:
373
+ del reasoning_effort
374
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
375
+ return _chat_messages_impl(
376
+ messages,
377
+ max_completion_tokens,
378
+ retries,
379
+ stage,
380
+ role="target",
381
+ timeout=timeout,
382
+ )
383
+
384
+
385
+ def chat_optimizer_messages(
386
+ messages: list[dict[str, Any]],
387
+ max_completion_tokens: int = 16384,
388
+ retries: int = 5,
389
+ stage: str = "optimizer",
390
+ reasoning_effort: str | None = None,
391
+ *,
392
+ tools: list[dict[str, Any]] | None = None,
393
+ tool_choice: str | dict[str, Any] | None = None,
394
+ return_message: bool = False,
395
+ timeout: float | None = None,
396
+ ) -> tuple[Any, dict[str, int]]:
397
+ del reasoning_effort
398
+ return _chat_messages_impl(
399
+ messages,
400
+ max_completion_tokens,
401
+ retries,
402
+ stage,
403
+ role="optimizer",
404
+ tools=tools,
405
+ tool_choice=tool_choice,
406
+ return_message=return_message,
407
+ timeout=timeout,
408
+ )
409
+
410
+
411
+ def chat_target_messages(
412
+ messages: list[dict[str, Any]],
413
+ max_completion_tokens: int = 16384,
414
+ retries: int = 5,
415
+ stage: str = "target",
416
+ reasoning_effort: str | None = None,
417
+ *,
418
+ tools: list[dict[str, Any]] | None = None,
419
+ tool_choice: str | dict[str, Any] | None = None,
420
+ return_message: bool = False,
421
+ timeout: float | None = None,
422
+ ) -> tuple[Any, dict[str, int]]:
423
+ del reasoning_effort
424
+ return _chat_messages_impl(
425
+ messages,
426
+ max_completion_tokens,
427
+ retries,
428
+ stage,
429
+ role="target",
430
+ tools=tools,
431
+ tool_choice=tool_choice,
432
+ return_message=return_message,
433
+ timeout=timeout,
434
+ )
435
+
436
+
437
+ def get_token_summary() -> dict[str, dict[str, int]]:
438
+ return tracker.summary()
439
+
440
+
441
+ def reset_token_tracker() -> None:
442
+ tracker.reset()
443
+
444
+
445
+ def set_reasoning_effort(effort: str | None) -> None:
446
+ del effort
447
+
448
+
449
+ def set_target_deployment(deployment: str) -> None:
450
+ TARGET_CONFIG.deployment = deployment or default_model_for_backend("qwen_chat")
451
+ os.environ["TARGET_DEPLOYMENT"] = TARGET_CONFIG.deployment
452
+
453
+
454
+ def set_optimizer_deployment(deployment: str) -> None:
455
+ OPTIMIZER_CONFIG.deployment = deployment or default_model_for_backend("qwen_chat")
456
+ os.environ["OPTIMIZER_DEPLOYMENT"] = OPTIMIZER_CONFIG.deployment
@@ -0,0 +1,236 @@
1
+ """Runtime backend router for ReflACT model calls."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from typing import Any
6
+
7
+ from . import azure_openai, claude_backend, codex_backend
8
+ from .common import normalize_backend_name
9
+
10
+
11
+ _ACTIVE_BACKEND = normalize_backend_name(
12
+ os.environ.get("REFLACT_MODEL_BACKEND", "azure_openai")
13
+ )
14
+
15
+
16
+ def _backend_module(name: str):
17
+ if name == "azure_openai":
18
+ return azure_openai
19
+ if name == "codex":
20
+ return codex_backend
21
+ if name == "claude":
22
+ return claude_backend
23
+ raise ValueError(f"Unknown backend: {name!r}")
24
+
25
+
26
+ def _all_backend_modules() -> list[Any]:
27
+ return [azure_openai, codex_backend, claude_backend]
28
+
29
+
30
+ def set_backend(name: str | None) -> str:
31
+ """Select the active model backend for subsequent calls."""
32
+ global _ACTIVE_BACKEND
33
+ normalized = normalize_backend_name(name)
34
+ if normalized not in {"azure_openai", "codex", "claude"}:
35
+ valid = ", ".join(sorted({"azure_openai", "codex", "claude"}))
36
+ raise ValueError(f"Unknown backend {name!r}. Expected one of: {valid}")
37
+ _ACTIVE_BACKEND = normalized
38
+ os.environ["REFLACT_MODEL_BACKEND"] = normalized
39
+ return _ACTIVE_BACKEND
40
+
41
+
42
+ def get_backend_name() -> str:
43
+ return _ACTIVE_BACKEND
44
+
45
+
46
+ def chat_optimizer(
47
+ system: str,
48
+ user: str,
49
+ max_completion_tokens: int = 16384,
50
+ retries: int = 5,
51
+ stage: str = "optimizer",
52
+ timeout: int | None = None,
53
+ ) -> tuple[str, dict[str, int]]:
54
+ return _backend_module(_ACTIVE_BACKEND).chat_optimizer(
55
+ system=system,
56
+ user=user,
57
+ max_completion_tokens=max_completion_tokens,
58
+ retries=retries,
59
+ stage=stage,
60
+ timeout=timeout,
61
+ )
62
+
63
+
64
+ def chat_target(
65
+ system: str,
66
+ user: str,
67
+ max_completion_tokens: int = 16384,
68
+ retries: int = 5,
69
+ stage: str = "target",
70
+ timeout: int | None = None,
71
+ ) -> tuple[str, dict[str, int]]:
72
+ return _backend_module(_ACTIVE_BACKEND).chat_target(
73
+ system=system,
74
+ user=user,
75
+ max_completion_tokens=max_completion_tokens,
76
+ retries=retries,
77
+ stage=stage,
78
+ timeout=timeout,
79
+ )
80
+
81
+
82
+ def chat_with_deployment(
83
+ deployment: str,
84
+ system: str,
85
+ user: str,
86
+ max_completion_tokens: int = 16384,
87
+ retries: int = 5,
88
+ stage: str = "custom",
89
+ timeout: int | None = None,
90
+ ) -> tuple[str, dict[str, int]]:
91
+ return _backend_module(_ACTIVE_BACKEND).chat_with_deployment(
92
+ deployment=deployment,
93
+ system=system,
94
+ user=user,
95
+ max_completion_tokens=max_completion_tokens,
96
+ retries=retries,
97
+ stage=stage,
98
+ timeout=timeout,
99
+ )
100
+
101
+
102
+ def chat_optimizer_messages(
103
+ messages: list[dict[str, Any]],
104
+ max_completion_tokens: int = 16384,
105
+ retries: int = 5,
106
+ stage: str = "optimizer",
107
+ *,
108
+ tools: list[dict[str, Any]] | None = None,
109
+ tool_choice: str | dict[str, Any] | None = None,
110
+ return_message: bool = False,
111
+ timeout: int | None = None,
112
+ ) -> tuple[Any, dict[str, int]]:
113
+ return _backend_module(_ACTIVE_BACKEND).chat_optimizer_messages(
114
+ messages=messages,
115
+ max_completion_tokens=max_completion_tokens,
116
+ retries=retries,
117
+ stage=stage,
118
+ tools=tools,
119
+ tool_choice=tool_choice,
120
+ return_message=return_message,
121
+ timeout=timeout,
122
+ )
123
+
124
+
125
+ def chat_target_messages(
126
+ messages: list[dict[str, Any]],
127
+ max_completion_tokens: int = 16384,
128
+ retries: int = 5,
129
+ stage: str = "target",
130
+ *,
131
+ tools: list[dict[str, Any]] | None = None,
132
+ tool_choice: str | dict[str, Any] | None = None,
133
+ return_message: bool = False,
134
+ timeout: int | None = None,
135
+ ) -> tuple[Any, dict[str, int]]:
136
+ return _backend_module(_ACTIVE_BACKEND).chat_target_messages(
137
+ messages=messages,
138
+ max_completion_tokens=max_completion_tokens,
139
+ retries=retries,
140
+ stage=stage,
141
+ tools=tools,
142
+ tool_choice=tool_choice,
143
+ return_message=return_message,
144
+ timeout=timeout,
145
+ )
146
+
147
+
148
+ def chat_messages_with_deployment(
149
+ deployment: str,
150
+ messages: list[dict[str, Any]],
151
+ max_completion_tokens: int = 16384,
152
+ retries: int = 5,
153
+ stage: str = "custom",
154
+ *,
155
+ tools: list[dict[str, Any]] | None = None,
156
+ tool_choice: str | dict[str, Any] | None = None,
157
+ return_message: bool = False,
158
+ timeout: int | None = None,
159
+ ) -> tuple[Any, dict[str, int]]:
160
+ return _backend_module(_ACTIVE_BACKEND).chat_messages_with_deployment(
161
+ deployment=deployment,
162
+ messages=messages,
163
+ max_completion_tokens=max_completion_tokens,
164
+ retries=retries,
165
+ stage=stage,
166
+ tools=tools,
167
+ tool_choice=tool_choice,
168
+ return_message=return_message,
169
+ timeout=timeout,
170
+ )
171
+
172
+
173
+ def get_token_summary() -> dict[str, dict[str, int]]:
174
+ return _backend_module(_ACTIVE_BACKEND).get_token_summary()
175
+
176
+
177
+ def reset_token_tracker() -> None:
178
+ _backend_module(_ACTIVE_BACKEND).reset_token_tracker()
179
+
180
+
181
+ def set_reasoning_effort(effort: str | None) -> None:
182
+ for module in _all_backend_modules():
183
+ module.set_reasoning_effort(effort)
184
+
185
+
186
+ def set_target_deployment(deployment: str) -> None:
187
+ for module in _all_backend_modules():
188
+ module.set_target_deployment(deployment)
189
+
190
+
191
+ def set_optimizer_deployment(deployment: str) -> None:
192
+ for module in _all_backend_modules():
193
+ module.set_optimizer_deployment(deployment)
194
+
195
+
196
+ def configure_azure_openai(
197
+ *,
198
+ endpoint: str | None = None,
199
+ api_version: str | None = None,
200
+ api_key: str | None = None,
201
+ auth_mode: str | None = None,
202
+ ad_scope: str | None = None,
203
+ managed_identity_client_id: str | None = None,
204
+ optimizer_endpoint: str | None = None,
205
+ optimizer_api_version: str | None = None,
206
+ optimizer_api_key: str | None = None,
207
+ optimizer_auth_mode: str | None = None,
208
+ optimizer_ad_scope: str | None = None,
209
+ optimizer_managed_identity_client_id: str | None = None,
210
+ target_endpoint: str | None = None,
211
+ target_api_version: str | None = None,
212
+ target_api_key: str | None = None,
213
+ target_auth_mode: str | None = None,
214
+ target_ad_scope: str | None = None,
215
+ target_managed_identity_client_id: str | None = None,
216
+ ) -> None:
217
+ azure_openai.configure_azure_openai(
218
+ endpoint=endpoint,
219
+ api_version=api_version,
220
+ api_key=api_key,
221
+ auth_mode=auth_mode,
222
+ ad_scope=ad_scope,
223
+ managed_identity_client_id=managed_identity_client_id,
224
+ optimizer_endpoint=optimizer_endpoint,
225
+ optimizer_api_version=optimizer_api_version,
226
+ optimizer_api_key=optimizer_api_key,
227
+ optimizer_auth_mode=optimizer_auth_mode,
228
+ optimizer_ad_scope=optimizer_ad_scope,
229
+ optimizer_managed_identity_client_id=optimizer_managed_identity_client_id,
230
+ target_endpoint=target_endpoint,
231
+ target_api_version=target_api_version,
232
+ target_api_key=target_api_key,
233
+ target_auth_mode=target_auth_mode,
234
+ target_ad_scope=target_ad_scope,
235
+ target_managed_identity_client_id=target_managed_identity_client_id,
236
+ )
@@ -0,0 +1,15 @@
1
+ """SkillOpt Optimizer -- skill update operations.
2
+
3
+ Analogous to the optimizer in neural network training: applies the computed
4
+ "gradient" (patches) to the current skill document to produce an updated
5
+ candidate skill.
6
+
7
+ Modules
8
+ -------
9
+ - skill: edit application (optimizer.step() / parameter update)
10
+ - clip: edit ranking and selection (gradient clipping)
11
+ - slow_update: longitudinal comparison and guidance (EMA / regularization)
12
+ - meta_skill: cross-epoch memory for optimizer context
13
+ """
14
+ from skillopt.optimizer.skill import apply_edit, apply_patch # noqa: F401
15
+ from skillopt.optimizer.clip import rank_and_select # noqa: F401