Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ docs/让*
tests/run_*.sh
tests/launch_*.py
*.launch.log
.codegraph/
uv.lock
# Superpowers smoke runs: raw agent output + local paths, share sanitized excerpts instead
smoke_results/
91 changes: 91 additions & 0 deletions skillopt/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from skillopt.model import azure_openai as _openai
from skillopt.model import claude_backend as _claude
from skillopt.model import codex_backend as _codex
from skillopt.model import hermes_backend as _hermes
from skillopt.model import minimax_backend as _minimax
from skillopt.model import openai_compatible_backend as _openai_compat
from skillopt.model import qwen_backend as _qwen
Expand Down Expand Up @@ -67,6 +68,10 @@ def set_backend(name: str | None) -> str:
set_optimizer_backend("openai_chat")
set_target_backend("minimax_chat")
return "minimax_chat"
if normalized in {"hermes", "hermes_chat"}:
set_optimizer_backend("hermes_chat")
set_target_backend("hermes_chat")
return "hermes_chat"
if normalized in {"openai_compatible", "openai_compatible_chat", "openai-compatible", "compat"}:
set_optimizer_backend("openai_compatible")
set_target_backend("openai_compatible")
Expand All @@ -90,6 +95,8 @@ def get_backend_name() -> str:
return "qwen_chat"
if optimizer == "openai_chat" and target == "minimax_chat":
return "minimax_chat"
if optimizer == "hermes_chat" and target == "hermes_chat":
return "hermes_chat"
if optimizer == "openai_chat" and target == "cursor_exec":
return "cursor_exec"
if optimizer == "openai_compatible" and target == "openai_compatible":
Expand Down Expand Up @@ -125,6 +132,15 @@ def chat_optimizer(
reasoning_effort=reasoning_effort,
timeout=timeout,
)
if get_optimizer_backend() == "hermes_chat":
return _hermes.chat_optimizer(
system=system,
user=user,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
timeout=timeout,
)
if get_optimizer_backend() == "minimax_chat":
return _minimax.chat_optimizer(
system=system,
Expand Down Expand Up @@ -202,6 +218,15 @@ def chat_target(
stage=stage,
reasoning_effort=reasoning_effort,
)
if get_target_backend() == "hermes_chat":
return _hermes.chat_target(
system=system,
user=user,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
timeout=timeout,
)
if get_target_backend() == "openai_compatible":
return _openai_compat.chat_target(
system=system,
Expand Down Expand Up @@ -263,6 +288,17 @@ def chat_optimizer_messages(
return_message=return_message,
timeout=timeout,
)
if get_optimizer_backend() == "hermes_chat":
return _hermes.chat_optimizer_messages(
messages=messages,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
tools=tools,
tool_choice=tool_choice,
return_message=return_message,
timeout=timeout,
)
if get_optimizer_backend() == "minimax_chat":
return _minimax.chat_target_messages(
messages=messages,
Expand Down Expand Up @@ -357,6 +393,17 @@ def chat_target_messages(
tool_choice=tool_choice,
return_message=return_message,
)
if get_target_backend() == "hermes_chat":
return _hermes.chat_target_messages(
messages=messages,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
tools=tools,
tool_choice=tool_choice,
return_message=return_message,
timeout=timeout,
)
if get_target_backend() == "openai_compatible":
return _openai_compat.chat_target_messages(
messages=messages,
Expand Down Expand Up @@ -400,6 +447,23 @@ def chat_messages_with_deployment(
return_message: bool = False,
timeout: int | None = None,
) -> tuple[Any, dict]:
if get_optimizer_backend() == "hermes_chat" and get_target_backend() == "hermes_chat":
# Route to Hermes only when BOTH backends are hermes_chat. When only
# one side is hermes_chat (dual-backend scenario) the function routes
# to OpenAI, which handles both sides via the generic OpenAI backend.
# A deployment-level ``role`` parameter would be cleaner but requires
# a broader API change — see the sibling function below.
return _hermes.chat_messages_with_deployment(
deployment=deployment,
messages=messages,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
tools=tools,
tool_choice=tool_choice,
return_message=return_message,
timeout=timeout,
)
return _openai.chat_messages_with_deployment(
deployment=deployment,
messages=messages,
Expand All @@ -424,6 +488,18 @@ def chat_with_deployment(
reasoning_effort: str | None = None,
timeout: int | None = None,
) -> tuple[str, dict]:
if get_optimizer_backend() == "hermes_chat" and get_target_backend() == "hermes_chat":
# Route to Hermes only when BOTH backends are hermes_chat. Same
# rationale as chat_messages_with_deployment above.
return _hermes.chat_with_deployment(
deployment=deployment,
system=system,
user=user,
max_completion_tokens=max_completion_tokens,
retries=retries,
stage=stage,
timeout=timeout,
)
return _openai.chat_with_deployment(
deployment=deployment,
system=system,
Expand Down Expand Up @@ -471,6 +547,17 @@ def get_token_summary() -> dict:
summary[stage]["prompt_tokens"] += values["prompt_tokens"]
summary[stage]["completion_tokens"] += values["completion_tokens"]
summary[stage]["total_tokens"] += values["total_tokens"]
hermes_summary = _hermes.get_token_summary()
for stage, values in hermes_summary.items():
if stage == "_total":
continue
if stage not in summary:
summary[stage] = values
continue
summary[stage]["calls"] += values["calls"]
summary[stage]["prompt_tokens"] += values["prompt_tokens"]
summary[stage]["completion_tokens"] += values["completion_tokens"]
summary[stage]["total_tokens"] += values["total_tokens"]
openai_compat_summary = _openai_compat.get_token_summary()
for stage, values in openai_compat_summary.items():
if stage == "_total":
Expand Down Expand Up @@ -515,6 +602,7 @@ def reset_token_tracker() -> None:
_claude.reset_token_tracker()
_qwen.reset_token_tracker()
_minimax.reset_token_tracker()
_hermes.reset_token_tracker()
_openai_compat.reset_token_tracker()
_codex.reset_token_tracker()

Expand Down Expand Up @@ -666,6 +754,7 @@ def set_reasoning_effort(effort: str | None) -> None:
_claude.set_reasoning_effort(effort)
_qwen.set_reasoning_effort(effort)
_minimax.set_reasoning_effort(effort)
_hermes.set_reasoning_effort(effort)
_openai_compat.set_reasoning_effort(effort)
_codex.set_reasoning_effort(effort)

Expand All @@ -675,6 +764,7 @@ def set_target_deployment(deployment: str) -> None:
_claude.set_target_deployment(deployment)
_qwen.set_target_deployment(deployment)
_minimax.set_target_deployment(deployment)
_hermes.set_target_deployment(deployment)
_openai_compat.set_target_deployment(deployment)
_codex.set_target_deployment(deployment)

Expand All @@ -683,5 +773,6 @@ def set_optimizer_deployment(deployment: str) -> None:
_openai.set_optimizer_deployment(deployment)
_claude.set_optimizer_deployment(deployment)
_qwen.set_optimizer_deployment(deployment)
_hermes.set_optimizer_deployment(deployment)
_openai_compat.set_optimizer_deployment(deployment)
_codex.set_optimizer_deployment(deployment)
11 changes: 6 additions & 5 deletions skillopt/model/backend_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ def set_optimizer_backend(backend: str) -> None:
"claude_chat",
"qwen_chat",
"minimax_chat",
"hermes_chat",
"openai_compatible",
"codex_exec",
}:
raise ValueError(
f"Unsupported optimizer backend: {OPTIMIZER_BACKEND!r}. "
"Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'minimax_chat', "
"'openai_compatible', and 'codex_exec'."
"'hermes_chat', 'openai_compatible', and 'codex_exec'."
)
os.environ["OPTIMIZER_BACKEND"] = OPTIMIZER_BACKEND

Expand All @@ -74,11 +75,11 @@ def get_optimizer_backend() -> str:
def set_target_backend(backend: str) -> None:
global TARGET_BACKEND
TARGET_BACKEND = normalize_backend_name(backend or "openai_chat")
if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "openai_compatible", "codex_exec", "claude_code_exec", "cursor_exec"}:
if TARGET_BACKEND not in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "openai_compatible", "codex_exec", "claude_code_exec", "cursor_exec", "hermes_chat"}:
raise ValueError(
f"Unsupported target backend: {TARGET_BACKEND!r}. "
"Supported values are 'openai_chat', 'claude_chat', 'qwen_chat', 'minimax_chat', "
"'openai_compatible', 'codex_exec', 'claude_code_exec', and 'cursor_exec'."
"'openai_compatible', 'codex_exec', 'claude_code_exec', 'cursor_exec', and 'hermes_chat'."
)
os.environ["TARGET_BACKEND"] = TARGET_BACKEND

Expand All @@ -90,20 +91,20 @@ def get_target_backend() -> str:
def is_target_exec_backend() -> bool:
return TARGET_BACKEND in {"codex_exec", "claude_code_exec", "cursor_exec"}


def is_optimizer_chat_backend() -> bool:
return OPTIMIZER_BACKEND in {
"openai_chat",
"claude_chat",
"qwen_chat",
"minimax_chat",
"hermes_chat",
"openai_compatible",
"codex_exec",
}


def is_target_chat_backend() -> bool:
return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "openai_compatible"}
return TARGET_BACKEND in {"openai_chat", "claude_chat", "qwen_chat", "minimax_chat", "hermes_chat", "openai_compatible"}


def configure_codex_exec(
Expand Down
3 changes: 3 additions & 0 deletions skillopt/model/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"cursor_exec": "composer-2.5",
"qwen_chat": "Qwen/Qwen3.5-4B",
"minimax_chat": "MiniMax-M2.7",
"hermes_chat": "hermes",
"openai_compatible": "gpt-4o-mini",
}

Expand All @@ -49,6 +50,8 @@
"qwen_chat": "qwen_chat",
"minimax": "minimax_chat",
"minimax_chat": "minimax_chat",
"hermes": "hermes_chat",
"hermes_chat": "hermes_chat",
"openai_compatible": "openai_compatible",
"openai_compatible_chat": "openai_compatible",
"openai-compatible": "openai_compatible",
Expand Down
Loading