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,185 @@
1
+ """Runtime backend configuration for optimizer/target model calls."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+
6
+ from skillopt.model.common import default_model_for_backend, normalize_backend_name
7
+
8
+
9
+ def _parse_bool(value: str | None, default: bool) -> bool:
10
+ if value is None:
11
+ return default
12
+ return str(value).strip().lower() in {"1", "true", "yes", "on"}
13
+
14
+
15
+ OPTIMIZER_BACKEND = normalize_backend_name(os.environ.get("OPTIMIZER_BACKEND", "openai_chat"))
16
+ TARGET_BACKEND = normalize_backend_name(os.environ.get("TARGET_BACKEND", "openai_chat"))
17
+
18
+ CODEX_EXEC_PATH = os.environ.get("CODEX_EXEC_PATH", "codex")
19
+ CODEX_EXEC_SANDBOX = os.environ.get("CODEX_EXEC_SANDBOX", "workspace-write")
20
+ CODEX_EXEC_PROFILE = os.environ.get("CODEX_EXEC_PROFILE", "")
21
+ CODEX_EXEC_FULL_AUTO = _parse_bool(os.environ.get("CODEX_EXEC_FULL_AUTO"), True)
22
+ CODEX_EXEC_REASONING_EFFORT = os.environ.get("CODEX_EXEC_REASONING_EFFORT", "none")
23
+ CODEX_EXEC_USE_SDK = os.environ.get("CODEX_EXEC_USE_SDK", "auto")
24
+ CODEX_EXEC_NETWORK_ACCESS = _parse_bool(os.environ.get("CODEX_EXEC_NETWORK_ACCESS"), False)
25
+ CODEX_EXEC_WEB_SEARCH = _parse_bool(os.environ.get("CODEX_EXEC_WEB_SEARCH"), False)
26
+ CODEX_EXEC_APPROVAL_POLICY = os.environ.get("CODEX_EXEC_APPROVAL_POLICY", "never")
27
+ CLAUDE_CODE_EXEC_PATH = os.environ.get("CLAUDE_CODE_EXEC_PATH", "claude")
28
+ CLAUDE_CODE_EXEC_PROFILE = os.environ.get("CLAUDE_CODE_EXEC_PROFILE", "")
29
+ CLAUDE_CODE_EXEC_USE_SDK = os.environ.get("CLAUDE_CODE_EXEC_USE_SDK", "auto")
30
+ CLAUDE_CODE_EXEC_EFFORT = os.environ.get("CLAUDE_CODE_EXEC_EFFORT", "medium")
31
+
32
+
33
+ def _parse_int(value: str | None, default: int) -> int:
34
+ if value is None:
35
+ return default
36
+ try:
37
+ return int(str(value).strip())
38
+ except ValueError:
39
+ return default
40
+
41
+
42
+ EXEC_EMPTY_RESPONSE_RETRIES = max(0, _parse_int(os.environ.get("EXEC_EMPTY_RESPONSE_RETRIES"), 1))
43
+ CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS = max(
44
+ 0,
45
+ _parse_int(os.environ.get("CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS"), 16384),
46
+ )
47
+
48
+
49
+ def set_optimizer_backend(backend: str) -> None:
50
+ global OPTIMIZER_BACKEND
51
+ OPTIMIZER_BACKEND = normalize_backend_name(backend or "openai_chat")
52
+ if OPTIMIZER_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}:
53
+ raise ValueError(
54
+ f"Unsupported optimizer backend: {OPTIMIZER_BACKEND!r}. "
55
+ "Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', and 'minimax_chat'."
56
+ )
57
+ os.environ["OPTIMIZER_BACKEND"] = OPTIMIZER_BACKEND
58
+
59
+
60
+ def get_optimizer_backend() -> str:
61
+ return OPTIMIZER_BACKEND
62
+
63
+
64
+ def set_target_backend(backend: str) -> None:
65
+ global TARGET_BACKEND
66
+ TARGET_BACKEND = normalize_backend_name(backend or "openai_chat")
67
+ if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "codex_exec", "claude_code_exec"}:
68
+ raise ValueError(
69
+ f"Unsupported target backend: {TARGET_BACKEND!r}. "
70
+ "Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'minimax_chat', 'codex_exec', and 'claude_code_exec'."
71
+ )
72
+ os.environ["TARGET_BACKEND"] = TARGET_BACKEND
73
+
74
+
75
+ def get_target_backend() -> str:
76
+ return TARGET_BACKEND
77
+
78
+
79
+ def is_target_exec_backend() -> bool:
80
+ return TARGET_BACKEND in {"codex_exec", "claude_code_exec"}
81
+
82
+
83
+ def is_optimizer_chat_backend() -> bool:
84
+ return OPTIMIZER_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}
85
+
86
+
87
+ def is_target_chat_backend() -> bool:
88
+ return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat"}
89
+
90
+
91
+ def configure_codex_exec(
92
+ *,
93
+ path: str | None = None,
94
+ sandbox: str | None = None,
95
+ profile: str | None = None,
96
+ full_auto: bool | None = None,
97
+ reasoning_effort: str | None = None,
98
+ use_sdk: str | None = None,
99
+ network_access: bool | None = None,
100
+ web_search: bool | None = None,
101
+ approval_policy: str | None = None,
102
+ ) -> None:
103
+ global CODEX_EXEC_PATH, CODEX_EXEC_SANDBOX, CODEX_EXEC_PROFILE, CODEX_EXEC_FULL_AUTO, CODEX_EXEC_REASONING_EFFORT, CODEX_EXEC_USE_SDK, CODEX_EXEC_NETWORK_ACCESS, CODEX_EXEC_WEB_SEARCH, CODEX_EXEC_APPROVAL_POLICY
104
+ if path is not None:
105
+ CODEX_EXEC_PATH = str(path).strip() or "codex"
106
+ os.environ["CODEX_EXEC_PATH"] = CODEX_EXEC_PATH
107
+ if sandbox is not None:
108
+ CODEX_EXEC_SANDBOX = str(sandbox).strip() or "workspace-write"
109
+ os.environ["CODEX_EXEC_SANDBOX"] = CODEX_EXEC_SANDBOX
110
+ if profile is not None:
111
+ CODEX_EXEC_PROFILE = str(profile).strip()
112
+ os.environ["CODEX_EXEC_PROFILE"] = CODEX_EXEC_PROFILE
113
+ if full_auto is not None:
114
+ CODEX_EXEC_FULL_AUTO = bool(full_auto)
115
+ os.environ["CODEX_EXEC_FULL_AUTO"] = "true" if CODEX_EXEC_FULL_AUTO else "false"
116
+ if reasoning_effort is not None:
117
+ CODEX_EXEC_REASONING_EFFORT = str(reasoning_effort).strip() or "none"
118
+ os.environ["CODEX_EXEC_REASONING_EFFORT"] = CODEX_EXEC_REASONING_EFFORT
119
+ if use_sdk is not None:
120
+ CODEX_EXEC_USE_SDK = str(use_sdk).strip().lower() or "auto"
121
+ os.environ["CODEX_EXEC_USE_SDK"] = CODEX_EXEC_USE_SDK
122
+ if network_access is not None:
123
+ CODEX_EXEC_NETWORK_ACCESS = bool(network_access)
124
+ os.environ["CODEX_EXEC_NETWORK_ACCESS"] = "true" if CODEX_EXEC_NETWORK_ACCESS else "false"
125
+ if web_search is not None:
126
+ CODEX_EXEC_WEB_SEARCH = bool(web_search)
127
+ os.environ["CODEX_EXEC_WEB_SEARCH"] = "true" if CODEX_EXEC_WEB_SEARCH else "false"
128
+ if approval_policy is not None:
129
+ CODEX_EXEC_APPROVAL_POLICY = str(approval_policy).strip() or "never"
130
+ os.environ["CODEX_EXEC_APPROVAL_POLICY"] = CODEX_EXEC_APPROVAL_POLICY
131
+
132
+
133
+ def get_codex_exec_config() -> dict[str, str | bool | int]:
134
+ return {
135
+ "path": CODEX_EXEC_PATH,
136
+ "sandbox": CODEX_EXEC_SANDBOX,
137
+ "profile": CODEX_EXEC_PROFILE,
138
+ "full_auto": CODEX_EXEC_FULL_AUTO,
139
+ "reasoning_effort": CODEX_EXEC_REASONING_EFFORT,
140
+ "use_sdk": CODEX_EXEC_USE_SDK,
141
+ "network_access": CODEX_EXEC_NETWORK_ACCESS,
142
+ "web_search": CODEX_EXEC_WEB_SEARCH,
143
+ "approval_policy": CODEX_EXEC_APPROVAL_POLICY,
144
+ "empty_response_retries": EXEC_EMPTY_RESPONSE_RETRIES,
145
+ }
146
+
147
+
148
+ def configure_claude_code_exec(
149
+ *,
150
+ path: str | None = None,
151
+ profile: str | None = None,
152
+ use_sdk: str | None = None,
153
+ effort: str | None = None,
154
+ max_thinking_tokens: int | str | None = None,
155
+ ) -> None:
156
+ global CLAUDE_CODE_EXEC_PATH, CLAUDE_CODE_EXEC_PROFILE, CLAUDE_CODE_EXEC_USE_SDK, CLAUDE_CODE_EXEC_EFFORT, CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS
157
+ if path is not None:
158
+ CLAUDE_CODE_EXEC_PATH = str(path).strip() or "claude"
159
+ os.environ["CLAUDE_CODE_EXEC_PATH"] = CLAUDE_CODE_EXEC_PATH
160
+ if profile is not None:
161
+ CLAUDE_CODE_EXEC_PROFILE = str(profile).strip()
162
+ os.environ["CLAUDE_CODE_EXEC_PROFILE"] = CLAUDE_CODE_EXEC_PROFILE
163
+ if use_sdk is not None:
164
+ CLAUDE_CODE_EXEC_USE_SDK = str(use_sdk).strip().lower() or "auto"
165
+ os.environ["CLAUDE_CODE_EXEC_USE_SDK"] = CLAUDE_CODE_EXEC_USE_SDK
166
+ if effort is not None:
167
+ CLAUDE_CODE_EXEC_EFFORT = str(effort).strip().lower() or "medium"
168
+ os.environ["CLAUDE_CODE_EXEC_EFFORT"] = CLAUDE_CODE_EXEC_EFFORT
169
+ if max_thinking_tokens is not None:
170
+ CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS = max(
171
+ 0,
172
+ _parse_int(str(max_thinking_tokens), 16384),
173
+ )
174
+ os.environ["CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS"] = str(CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS)
175
+
176
+
177
+ def get_claude_code_exec_config() -> dict[str, str | int]:
178
+ return {
179
+ "path": CLAUDE_CODE_EXEC_PATH,
180
+ "profile": CLAUDE_CODE_EXEC_PROFILE,
181
+ "use_sdk": CLAUDE_CODE_EXEC_USE_SDK,
182
+ "effort": CLAUDE_CODE_EXEC_EFFORT,
183
+ "max_thinking_tokens": CLAUDE_CODE_EXEC_MAX_THINKING_TOKENS,
184
+ "empty_response_retries": EXEC_EMPTY_RESPONSE_RETRIES,
185
+ }
@@ -0,0 +1,371 @@
1
+ """Claude CLI chat backend for ReflACT."""
2
+ from __future__ import annotations
3
+
4
+ import base64
5
+ import json
6
+ import mimetypes
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import tempfile
11
+ import time
12
+ from typing import Any
13
+ from urllib.parse import unquote, urlparse
14
+
15
+ from skillopt.model.common import CompatAssistantMessage, CompatToolCall, CompatToolFunction, default_model_for_backend, tracker
16
+
17
+ CLAUDE_BIN = os.environ.get("CLAUDE_CLI_BIN", "claude")
18
+ CLAUDE_PERMISSION_MODE = os.environ.get("CLAUDE_PERMISSION_MODE", "dontAsk")
19
+ CLAUDE_SETTING_SOURCES = os.environ.get("CLAUDE_SETTING_SOURCES", "user,project")
20
+ CLAUDE_ALLOW_ATTACHMENT_READ = os.environ.get("CLAUDE_ALLOW_ATTACHMENT_READ", "1").strip().lower() not in {"0", "false", "no"}
21
+
22
+ OPTIMIZER_DEPLOYMENT = os.environ.get("OPTIMIZER_DEPLOYMENT", "claude-sonnet-4-6")
23
+ TARGET_DEPLOYMENT = os.environ.get("TARGET_DEPLOYMENT", "claude-sonnet-4-6")
24
+ REASONING_EFFORT: str | None = None
25
+ _VALID_EFFORTS = {"low", "medium", "high", "xhigh", "max"}
26
+
27
+
28
+ def _parse_data_uri(url: str) -> tuple[bytes, str]:
29
+ header, data = url.split(",", 1)
30
+ mime = header[5:].split(";", 1)[0] or "image/png"
31
+ return base64.b64decode(data), mime
32
+
33
+
34
+ def _content_to_text(content: Any, attachments: list[dict[str, Any]], *, image_counter: int) -> tuple[str, int]:
35
+ if isinstance(content, str):
36
+ return content, image_counter
37
+ if not isinstance(content, list):
38
+ return str(content), image_counter
39
+ parts: list[str] = []
40
+ for item in content:
41
+ if not isinstance(item, dict):
42
+ continue
43
+ item_type = item.get("type")
44
+ if item_type == "text":
45
+ parts.append(str(item.get("text", "")))
46
+ continue
47
+ if item_type != "image_url":
48
+ continue
49
+ image_counter += 1
50
+ label = f"[Attached image {image_counter}]"
51
+ parts.append(label)
52
+ image_url = item.get("image_url", {}) or {}
53
+ url = str(image_url.get("url", "") or "")
54
+ if not url:
55
+ continue
56
+ if url.startswith("data:") and ";base64," in url:
57
+ data, mime = _parse_data_uri(url)
58
+ attachments.append({"bytes": data, "mime": mime, "label": label})
59
+ continue
60
+ if url.startswith("file://"):
61
+ parsed = urlparse(url)
62
+ path = unquote(parsed.path)
63
+ if path:
64
+ attachments.append({"path": path, "label": label})
65
+ continue
66
+ if os.path.exists(url):
67
+ attachments.append({"path": url, "label": label})
68
+ return "".join(parts), image_counter
69
+
70
+
71
+ def _simplify_tool_schemas(tools: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
72
+ simplified: list[dict[str, Any]] = []
73
+ for tool in tools or []:
74
+ function = tool.get("function", tool)
75
+ simplified.append({
76
+ "name": function.get("name", ""),
77
+ "description": function.get("description", ""),
78
+ "parameters": function.get("parameters", {}),
79
+ })
80
+ return simplified
81
+
82
+
83
+ def _build_prompt_from_messages(messages: list[dict[str, Any]], *, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, structured_output: bool = False) -> tuple[str, str, list[dict[str, Any]]]:
84
+ system_parts: list[str] = []
85
+ history_parts: list[str] = []
86
+ attachments: list[dict[str, Any]] = []
87
+ image_counter = 0
88
+
89
+ def _history_line(label: str, body: str) -> str:
90
+ stripped = body.strip()
91
+ if not stripped:
92
+ return f"- {label}:"
93
+ indented = stripped.replace("\n", "\n ")
94
+ return f"- {label}: {indented}"
95
+
96
+ for message in messages:
97
+ role = str(message.get("role", "user"))
98
+ text, image_counter = _content_to_text(message.get("content", ""), attachments, image_counter=image_counter)
99
+ if role == "system":
100
+ if text.strip():
101
+ system_parts.append(text.strip())
102
+ continue
103
+ if role == "assistant":
104
+ block = _history_line("Assistant", text)
105
+ tool_calls = message.get("tool_calls") or []
106
+ if tool_calls:
107
+ simplified_calls = []
108
+ for tool_call in tool_calls:
109
+ function = tool_call.get("function", {}) or {}
110
+ simplified_calls.append({
111
+ "name": function.get("name", ""),
112
+ "arguments": function.get("arguments", "{}"),
113
+ })
114
+ block += "\n Compatibility tool requests:\n" + json.dumps(simplified_calls, ensure_ascii=False, indent=2)
115
+ history_parts.append(block)
116
+ continue
117
+ if role == "tool":
118
+ tool_call_id = str(message.get("tool_call_id", "") or "")
119
+ history_parts.append(_history_line(f"Tool result (tool_call_id={tool_call_id})", text))
120
+ continue
121
+ history_parts.append(_history_line(role.capitalize(), text))
122
+
123
+ prompt_parts: list[str] = []
124
+ if tools:
125
+ simplified_tools = _simplify_tool_schemas(tools)
126
+ prompt_parts.append("Available compatibility tools:\n" + json.dumps(simplified_tools, ensure_ascii=False, indent=2))
127
+ prompt_parts.append("Do not execute these compatibility tools yourself. If you need one, request it in `tool_calls`. Each `arguments` field must be a JSON string.")
128
+ if tool_choice == "required":
129
+ prompt_parts.append("Tool choice policy: you must request at least one compatibility tool.")
130
+ elif isinstance(tool_choice, dict) and tool_choice.get("type") == "function":
131
+ function = tool_choice.get("function", {}) or {}
132
+ prompt_parts.append(f"Tool choice policy: you must request the compatibility tool `{function.get('name', '')}`.")
133
+ history_text = "\n".join(part for part in history_parts if part).strip()
134
+ if history_text:
135
+ prompt_parts.append("History:\n" + history_text)
136
+ if structured_output:
137
+ prompt_parts.append("Return only JSON matching the provided schema.")
138
+ if tools:
139
+ prompt_parts.append("Set `content` to the assistant-visible reply. Set `tool_calls` to an empty array when no compatibility tool is needed.")
140
+ else:
141
+ prompt_parts.append("Answer the latest user request.")
142
+ return "\n\n".join(part for part in system_parts if part).strip(), "\n\n".join(prompt_parts), attachments
143
+
144
+
145
+ def _copy_attachments_to_temp(attachments: list[dict[str, Any]], temp_dir: str) -> list[dict[str, str]]:
146
+ copied: list[dict[str, str]] = []
147
+ for index, attachment in enumerate(attachments, 1):
148
+ source_path = attachment.get("path")
149
+ if source_path:
150
+ source_path = str(source_path)
151
+ source_suffix = os.path.splitext(source_path)[1]
152
+ target_path = os.path.join(temp_dir, f"image_{index}{source_suffix or '.bin'}")
153
+ shutil.copyfile(source_path, target_path)
154
+ copied.append({"path": target_path, "label": str(attachment.get("label", ""))})
155
+ continue
156
+ mime = str(attachment.get("mime", "image/png"))
157
+ suffix = mimetypes.guess_extension(mime) or ".png"
158
+ target_path = os.path.join(temp_dir, f"image_{index}{suffix}")
159
+ with open(target_path, "wb") as f:
160
+ f.write(attachment.get("bytes", b"") or b"")
161
+ copied.append({"path": target_path, "label": str(attachment.get("label", ""))})
162
+ return copied
163
+
164
+
165
+ def _append_attachment_instructions(prompt: str, copied_attachments: list[dict[str, str]]) -> str:
166
+ if not copied_attachments or not CLAUDE_ALLOW_ATTACHMENT_READ:
167
+ return prompt
168
+ lines = [
169
+ "Attached image files:",
170
+ *[f"- {item['label'] or f'Attached image {index}'}: {item['path']}" for index, item in enumerate(copied_attachments, 1)],
171
+ "If you need to inspect an attached image, you may use the built-in `Read` tool on those listed paths only. Do not use built-in tools for any other purpose.",
172
+ ]
173
+ return prompt.rstrip() + "\n\n" + "\n".join(lines)
174
+
175
+
176
+ def _usage_from_result(result_event: dict[str, Any] | None) -> dict[str, int]:
177
+ usage = (result_event or {}).get("usage", {}) or {}
178
+ input_tokens = int(usage.get("input_tokens", 0) or 0)
179
+ output_tokens = int(usage.get("output_tokens", 0) or 0)
180
+ return {
181
+ "prompt_tokens": input_tokens,
182
+ "completion_tokens": output_tokens,
183
+ "total_tokens": input_tokens + output_tokens,
184
+ }
185
+
186
+
187
+ def _extract_result(event_stream: list[dict[str, Any]]) -> tuple[str, dict[str, Any] | None]:
188
+ result_event = None
189
+ for event in reversed(event_stream):
190
+ if event.get("type") == "result":
191
+ result_event = event
192
+ break
193
+ if result_event is None:
194
+ raise RuntimeError("Claude backend did not return a result event.")
195
+ content = result_event.get("result") or result_event.get("content") or ""
196
+ return str(content), result_event
197
+
198
+
199
+ def _check_claude_error(stderr_text: str, model: str) -> None:
200
+ lowered = stderr_text.lower()
201
+ if "invalid api key" in lowered or "authentication" in lowered or "login" in lowered:
202
+ raise RuntimeError("Claude CLI is not logged in. Run `claude auth login` (or start `claude` and use `/login`) first.")
203
+ if "unknown model" in lowered or "not available" in lowered or "invalid model" in lowered:
204
+ default_model = default_model_for_backend("claude")
205
+ raise RuntimeError(f"Claude backend tried to use model {model!r}, but your current Claude CLI/account rejected it. Try an available Claude model such as {default_model!r}.")
206
+
207
+
208
+ def _normalize_reasoning_effort(effort: str | None) -> str | None:
209
+ normalized = str(effort or "").strip().lower()
210
+ if not normalized or normalized == "off":
211
+ return None
212
+ if normalized in _VALID_EFFORTS:
213
+ return normalized
214
+ return None
215
+
216
+
217
+ def _assistant_message_schema() -> dict[str, Any]:
218
+ return {
219
+ "type": "object",
220
+ "properties": {
221
+ "content": {"type": "string"},
222
+ "tool_calls": {
223
+ "type": "array",
224
+ "items": {
225
+ "type": "object",
226
+ "properties": {
227
+ "name": {"type": "string"},
228
+ "arguments": {"type": "string"},
229
+ },
230
+ "required": ["name", "arguments"],
231
+ "additionalProperties": False,
232
+ },
233
+ },
234
+ },
235
+ "required": ["content", "tool_calls"],
236
+ "additionalProperties": False,
237
+ }
238
+
239
+
240
+ def _assistant_message_schema_wrapper() -> str:
241
+ return json.dumps(_assistant_message_schema(), ensure_ascii=False)
242
+
243
+
244
+ def _run_claude_print(*, system: str, prompt: str, model: str, tools: list[dict[str, Any]] | None, tool_choice: str | dict[str, Any] | None, return_message: bool, timeout: int | None, attachments: list[dict[str, Any]] | None = None) -> tuple[str, dict[str, Any], dict[str, int]]:
245
+ effort = _normalize_reasoning_effort(REASONING_EFFORT)
246
+ with tempfile.TemporaryDirectory(prefix="skillopt_claude_") as temp_dir:
247
+ copied_attachments = _copy_attachments_to_temp(attachments or [], temp_dir)
248
+ prompt_for_cli = _append_attachment_instructions(prompt, copied_attachments)
249
+ cmd = [CLAUDE_BIN, "-p", "--output-format", "json", "--permission-mode", CLAUDE_PERMISSION_MODE, "--add-dir", temp_dir]
250
+ if model:
251
+ cmd.extend(["--model", model])
252
+ if CLAUDE_SETTING_SOURCES:
253
+ cmd.extend(["--setting-sources", CLAUDE_SETTING_SOURCES])
254
+ if system:
255
+ # Write the system prompt to a file, not argv: here the skill being
256
+ # optimized IS the system prompt, and SkillOpt grows it over training,
257
+ # so past ~30 KB it would re-hit the Windows argv cap (WinError 206).
258
+ # The CLI reads it via --append-system-prompt-file.
259
+ system_path = os.path.join(temp_dir, "system_prompt.txt")
260
+ with open(system_path, "w", encoding="utf-8") as system_fh:
261
+ system_fh.write(system)
262
+ cmd.extend(["--append-system-prompt-file", system_path])
263
+ if effort:
264
+ cmd.extend(["--effort", effort])
265
+ structured_output = bool(return_message)
266
+ if structured_output:
267
+ cmd.extend(["--schema", _assistant_message_schema_wrapper()])
268
+ # Feed the prompt via stdin (and the system prompt via a file, above), not
269
+ # argv: on Windows the whole command line is capped at ~32 KB and large
270
+ # optimizer prompts / grown skills overflow it → [WinError 206]. Pin UTF-8
271
+ # so a zh-CN default codepage (cp936) can't raise UnicodeEncodeError on
272
+ # emoji / non-GBK glyphs before the CLI even starts.
273
+ proc = subprocess.run(cmd, input=prompt_for_cli, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout or 300, cwd=temp_dir)
274
+ stderr_text = (proc.stderr or "").strip()
275
+ if proc.returncode != 0:
276
+ _check_claude_error(stderr_text, model)
277
+ raise RuntimeError(stderr_text or f"Claude CLI exited with code {proc.returncode}")
278
+ stream = []
279
+ for raw_line in (proc.stdout or "").splitlines():
280
+ raw_line = raw_line.strip()
281
+ if not raw_line:
282
+ continue
283
+ try:
284
+ stream.append(json.loads(raw_line))
285
+ except json.JSONDecodeError:
286
+ continue
287
+ raw_text, result_event = _extract_result(stream)
288
+ usage_info = _usage_from_result(result_event)
289
+ return raw_text, result_event or {}, usage_info
290
+
291
+
292
+ def _compat_message_from_payload(payload: Any) -> CompatAssistantMessage:
293
+ if not isinstance(payload, dict):
294
+ return CompatAssistantMessage(content=str(payload or ""), tool_calls=[])
295
+ content = str(payload.get("content", "") or "")
296
+ tool_calls: list[CompatToolCall] = []
297
+ for index, tool_call in enumerate(payload.get("tool_calls", []) or [], start=1):
298
+ name = str(tool_call.get("name", "") or "")
299
+ arguments = str(tool_call.get("arguments", "{}") or "{}")
300
+ tool_calls.append(CompatToolCall(id=f"claude_tool_{index}", function=CompatToolFunction(name=name, arguments=arguments)))
301
+ return CompatAssistantMessage(content=content, tool_calls=tool_calls)
302
+
303
+
304
+ def _call_messages(messages: list[dict[str, Any]], max_completion_tokens: int, retries: int, stage: str, *, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, return_message: bool = False, deployment: str | None = None, timeout: int | None = None) -> tuple[Any, dict[str, int]]:
305
+ del max_completion_tokens
306
+ system, prompt, attachments = _build_prompt_from_messages(messages, tools=tools, tool_choice=tool_choice, structured_output=return_message)
307
+ model = deployment or TARGET_DEPLOYMENT
308
+ last_err = None
309
+ for attempt in range(retries):
310
+ try:
311
+ raw_text, payload, usage_info = _run_claude_print(system=system, prompt=prompt, model=model, tools=tools, tool_choice=tool_choice, return_message=return_message, timeout=timeout, attachments=attachments)
312
+ tracker.record(stage, usage_info["prompt_tokens"], usage_info["completion_tokens"])
313
+ if return_message:
314
+ return _compat_message_from_payload(payload.get("result", payload)), usage_info
315
+ return raw_text, usage_info
316
+ except Exception as e: # noqa: BLE001
317
+ last_err = e
318
+ time.sleep(min(2 ** attempt, 15))
319
+ raise RuntimeError(f"Claude backend failed after {retries} retries: {last_err}")
320
+
321
+
322
+ def chat_optimizer(system: str, user: str, max_completion_tokens: int = 16384, retries: int = 5, stage: str = "optimizer", timeout: int | None = None) -> tuple[str, dict[str, int]]:
323
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
324
+ return _call_messages(messages, max_completion_tokens, retries, stage, deployment=OPTIMIZER_DEPLOYMENT, timeout=timeout)
325
+
326
+
327
+ def chat_target(system: str, user: str, max_completion_tokens: int = 16384, retries: int = 5, stage: str = "target", timeout: int | None = None) -> tuple[str, dict[str, int]]:
328
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
329
+ return _call_messages(messages, max_completion_tokens, retries, stage, deployment=TARGET_DEPLOYMENT, timeout=timeout)
330
+
331
+
332
+ def chat_with_deployment(deployment: str, system: str, user: str, max_completion_tokens: int = 16384, retries: int = 5, stage: str = "custom", timeout: int | None = None) -> tuple[str, dict[str, int]]:
333
+ messages = [{"role": "system", "content": system}, {"role": "user", "content": user}]
334
+ return _call_messages(messages, max_completion_tokens, retries, stage, deployment=deployment, timeout=timeout)
335
+
336
+
337
+ def chat_optimizer_messages(messages: list[dict[str, Any]], max_completion_tokens: int = 16384, retries: int = 5, stage: str = "optimizer", *, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, return_message: bool = False, timeout: int | None = None) -> tuple[Any, dict[str, int]]:
338
+ return _call_messages(messages, max_completion_tokens, retries, stage, tools=tools, tool_choice=tool_choice, return_message=return_message, deployment=OPTIMIZER_DEPLOYMENT, timeout=timeout)
339
+
340
+
341
+ def chat_target_messages(messages: list[dict[str, Any]], max_completion_tokens: int = 16384, retries: int = 5, stage: str = "target", *, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, return_message: bool = False, timeout: int | None = None) -> tuple[Any, dict[str, int]]:
342
+ return _call_messages(messages, max_completion_tokens, retries, stage, tools=tools, tool_choice=tool_choice, return_message=return_message, deployment=TARGET_DEPLOYMENT, timeout=timeout)
343
+
344
+
345
+ def chat_messages_with_deployment(deployment: str, messages: list[dict[str, Any]], max_completion_tokens: int = 16384, retries: int = 5, stage: str = "custom", *, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, return_message: bool = False, timeout: int | None = None) -> tuple[Any, dict[str, int]]:
346
+ return _call_messages(messages, max_completion_tokens, retries, stage, tools=tools, tool_choice=tool_choice, return_message=return_message, deployment=deployment, timeout=timeout)
347
+
348
+
349
+ def get_token_summary() -> dict[str, dict[str, int]]:
350
+ return tracker.summary()
351
+
352
+
353
+ def reset_token_tracker() -> None:
354
+ tracker.reset()
355
+
356
+
357
+ def set_reasoning_effort(effort: str | None) -> None:
358
+ global REASONING_EFFORT
359
+ REASONING_EFFORT = effort if effort else None
360
+
361
+
362
+ def set_target_deployment(deployment: str) -> None:
363
+ global TARGET_DEPLOYMENT
364
+ TARGET_DEPLOYMENT = deployment or default_model_for_backend("claude")
365
+ os.environ["TARGET_DEPLOYMENT"] = TARGET_DEPLOYMENT
366
+
367
+
368
+ def set_optimizer_deployment(deployment: str) -> None:
369
+ global OPTIMIZER_DEPLOYMENT
370
+ OPTIMIZER_DEPLOYMENT = deployment or default_model_for_backend("claude")
371
+ os.environ["OPTIMIZER_DEPLOYMENT"] = OPTIMIZER_DEPLOYMENT