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.
- package/package.json +1 -1
- package/template/skills/skillopt-sleep/SKILL.md +42 -0
- package/template/skills/skillopt-sleep/configs/_base_/default.yaml +103 -0
- package/template/skills/skillopt-sleep/configs/alfworld/default.yaml +29 -0
- package/template/skills/skillopt-sleep/configs/docvqa/default.yaml +28 -0
- package/template/skills/skillopt-sleep/configs/features/soft_gate.yaml +47 -0
- package/template/skills/skillopt-sleep/configs/livemathematicianbench/default.yaml +22 -0
- package/template/skills/skillopt-sleep/configs/officeqa/default.yaml +34 -0
- package/template/skills/skillopt-sleep/configs/searchqa/default.yaml +32 -0
- package/template/skills/skillopt-sleep/configs/spreadsheetbench/default.yaml +34 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/__init__.py +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/config.py +282 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/__init__.py +7 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/datasets/base.py +512 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/engine/trainer.py +2379 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/README.md +43 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/config_template.yaml +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/env_template.py +151 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/_template/loader_template.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/adapter.py +428 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/dataloader.py +123 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_error.md +55 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/analyst_success.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_no_history.md +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_history.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/prompts/rollout_with_memory.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/rollout.py +366 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/skills/initial.md +45 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/__init__.py +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_envs.py +221 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_projection.py +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/alfworld_prompts.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/config_tw.yaml +145 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_base.py +84 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/env_manager.py +139 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/alfworld/vendor/memory.py +87 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/base.py +329 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/adapter.py +90 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/dataloader.py +61 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/evaluator.py +113 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_error.md +35 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/analyst_success.md +24 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/rollout.py +391 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/docvqa/skills/initial.md +11 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/adapter.py +129 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/dataloader.py +308 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/evaluator.py +62 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/prompts/rollout_system.md +12 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/rollout.py +434 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/livemathematicianbench/skills/initial.md +16 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/adapter.py +112 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/dataloader.py +71 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/evaluator.py +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_error.md +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/analyst_success.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/prompts/rollout_system.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/rollout.py +799 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/skills/initial.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/officeqa/tool_runtime.py +552 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/__init__.py +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/adapter.py +96 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/dataloader.py +42 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/evaluator.py +100 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/prompts/rollout_system.md +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/rollout.py +494 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/searchqa/skills/initial.md +3 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/__init__.py +5 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/adapter.py +159 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/codegen_agent.py +731 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/dataloader.py +37 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/evaluator.py +158 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/executor.py +67 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_error.md +46 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/analyst_success.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/codegen_system.md +1 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/critical_rules.md +9 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/prompts/react_system.md +21 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/react_agent.py +395 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/reflect.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/rollout.py +979 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/envs/spreadsheetbench/skills/initial.md +56 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/__init__.py +13 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/evaluation/gate.py +148 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/aggregate.py +253 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/gradient/reflect.py +635 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/__init__.py +514 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/azure_openai.py +915 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/backend_config.py +185 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/claude_backend.py +371 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_backend.py +666 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/codex_harness.py +1057 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/common.py +229 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/minimax_backend.py +277 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/qwen_backend.py +456 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/model/router.py +236 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/__init__.py +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/appendix.py +156 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/clip.py +109 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/lr_autonomous.py +108 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/meta_skill.py +79 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/rewrite.py +59 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/scheduler.py +127 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/select.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill.py +201 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/skill_aware.py +206 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/slow_update.py +396 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/optimizer/update_modes.py +135 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/__init__.py +63 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error.md +41 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_full_rewrite.md +32 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_error_rewrite.md +44 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success.md +36 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_full_rewrite.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/analyst_success_rewrite.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/lr_autonomous.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure.md +30 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_failure_rewrite.md +26 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final.md +33 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_final_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_full_rewrite.md +28 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/merge_success_rewrite.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/meta_skill.md +40 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking.md +20 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/ranking_rewrite.md +15 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/rewrite_skill.md +25 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/prompts/slow_update.md +60 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/scheduler/__init__.py +8 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/types.py +306 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/__init__.py +4 -0
- package/template/skills/skillopt-sleep/scripts/framework/skillopt/utils/json_utils.py +172 -0
- 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
|