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
2 changes: 1 addition & 1 deletion INTERACTIVE_MODE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ works — that's how you see *what it believes and why*, not just what it's doin
| `--cli` | off (web is default) | Use the **terminal** interface instead of the browser |
| `--port N` | `7890` | Port for the web UI (auto-retries if taken) |
| `--no-browser` | off | Start the web server but don't auto-open the browser |
| `--backend {cli\|anthropic_api\|openrouter}` | from config | Which backend powers the manager's own reasoning |
| `--backend {cli\|anthropic_api\|openrouter\|requesty}` | from config | Which backend powers the manager's own reasoning |

**Examples** (`<idea_id>` is the full ID that `submit` printed, e.g.
`titanic_survival_prediction_20260606_213145_67d058cf`)
Expand Down
3 changes: 2 additions & 1 deletion config/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

manager:
# LLM backend for manager reasoning
# Options: "cli" (uses claude -p, no extra keys), "anthropic_api", "openrouter"
# Options: "cli" (uses claude -p, no extra keys), "anthropic_api", "openrouter", "requesty"
# Can also be set via NEURICO_MANAGER_BACKEND env var
llm_backend: cli

# Model to use for manager reasoning (null = default for backend)
# For cli: uses whatever claude version is installed
# For anthropic_api: e.g., "claude-sonnet-4-20250514"
# For openrouter: e.g., "anthropic/claude-sonnet-4"
# For requesty: e.g., "anthropic/claude-sonnet-4" (OpenAI-compatible, needs REQUESTY_API_KEY)
# Can also be set via NEURICO_MANAGER_MODEL env var
llm_model: null

Expand Down
72 changes: 70 additions & 2 deletions src/interactive/llm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
LLM Backend Abstraction

Provides a unified interface for calling LLMs, whether via CLI (claude -p)
or API (Anthropic SDK / OpenRouter). The backend is configured by the user
or API (Anthropic SDK / OpenRouter / Requesty). The backend is configured by the user
in config/manager.yaml or .env.
"""

Expand Down Expand Up @@ -40,7 +40,7 @@ class LLMBackend:
def __init__(self, backend: str = "cli", model: Optional[str] = None):
"""
Args:
backend: "cli", "anthropic_api", or "openrouter"
backend: "cli", "anthropic_api", "openrouter", or "requesty"
model: Model name override (None = default for backend)
"""
self.backend = backend
Expand All @@ -65,6 +65,8 @@ def send(self, messages: List[Dict[str, Any]],
return self._send_anthropic_api(messages, tools)
elif self.backend == "openrouter":
return self._send_openrouter(messages, tools)
elif self.backend == "requesty":
return self._send_requesty(messages, tools)
else:
raise ValueError(f"Unknown backend: {self.backend}")

Expand Down Expand Up @@ -368,6 +370,72 @@ def _send_openrouter(self, messages: List[Dict[str, Any]],

return LLMResponse(text=text, tool_calls=tool_calls, raw=data)

def _send_requesty(self, messages: List[Dict[str, Any]],
tools: Optional[List[Dict[str, Any]]] = None) -> LLMResponse:
"""Send via Requesty (OpenAI-compatible router). Requires REQUESTY_API_KEY."""
try:
import httpx
except ImportError:
raise ImportError(
"httpx package required for Requesty backend. "
"Install with: pip install httpx"
)

api_key = os.environ.get("REQUESTY_API_KEY")
if not api_key:
raise ValueError("REQUESTY_API_KEY environment variable required for requesty backend")

model = self.model or "anthropic/claude-sonnet-4"

payload = {
"model": model,
"messages": [{"role": m["role"], "content": m["content"]} for m in messages],
"max_tokens": 4096,
}

if tools:
payload["tools"] = [{
"type": "function",
"function": {
"name": t["name"],
"description": t.get("description", ""),
"parameters": t.get("parameters", {})
}
} for t in tools]

response = httpx.post(
"https://router.requesty.ai/v1/chat/completions",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json=payload,
timeout=120
)
response.raise_for_status()
data = response.json()

# Parse OpenAI-compatible response
choice = data["choices"][0]["message"]
text = choice.get("content", "") or ""
tool_calls = []

for tc in choice.get("tool_calls", []):
func = tc.get("function", {})
args = func.get("arguments", "{}")
if isinstance(args, str):
try:
args = json.loads(args)
except json.JSONDecodeError:
args = {"raw": args}
tool_calls.append(ToolCall(
id=tc.get("id", ""),
name=func.get("name", ""),
arguments=args
))

return LLMResponse(text=text, tool_calls=tool_calls, raw=data)


def create_backend(config: Dict[str, Any]) -> LLMBackend:
"""
Expand Down
2 changes: 1 addition & 1 deletion src/interactive/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def main():
parser.add_argument(
"--backend",
default=None,
choices=["cli", "anthropic_api", "openrouter"],
choices=["cli", "anthropic_api", "openrouter", "requesty"],
help="LLM backend for manager reasoning (default: from config)"
)
parser.add_argument(
Expand Down