mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-29 18:40:38 +00:00
* chore: create learning subdirectory structure (routing, agents, intelligence) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: extract classify_query to routing/_utils.py Move the classify_query() function and its regex patterns into a shared utility module so multiple routing policies can import it without depending on the full trace_policy module. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move routing files to learning/routing/ subdirectory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: create LearnedRouterPolicy merging trace-driven + SFT routing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add conditional Algolia DocSearch integration Add Algolia DocSearch as an optional search upgrade — native lunr.js search remains the default until credentials are configured. Includes CDN assets, Jinja2 conditional config injection, init script with graceful fallback, light/dark theme CSS, improved search tokenization for snake_case/dotted identifiers, and search boosts for key pages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move agent_evolver and skill_discovery to learning/agents/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move learning/orchestrator to learning/intelligence/orchestrator Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: delete removed learning policies, rewrite __init__.py, clean up api_routes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add SFT/GRPO/DSPy/GEPA config dataclasses, update LearningConfig Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add general-purpose SFT trainer (intelligence/sft_trainer.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update stale imports in multi_model_router example Update imports to use new learning/routing/ paths after the subdirectory reorganization. Replace BanditRouterPolicy with LearnedRouterPolicy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add general-purpose GRPO trainer (intelligence/grpo_trainer.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add DSPy agent optimizer (agents/dspy_optimizer.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add GEPA agent optimizer (agents/gepa_optimizer.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add learning-dspy and learning-gepa optional dependency extras Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update integration test to check for learned policy instead of grpo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: clean up stale APIs and unused params in examples - deep_research: remove system_prompt and max_turns params not accepted by Jarvis.ask(), inline system prompt into the query instead - doc_qa: remove unused --top-k CLI arg that was never passed to the API - multi_model_router: fix select_model() call to match single-arg signature Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: import SFT/GRPO trainers in intelligence/__init__.py for registry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove .md file changes from PR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: restore search boost frontmatter for key docs pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
"""Tests for orchestrator RL environment."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from openjarvis.core.types import ToolResult
|
|
from openjarvis.learning.intelligence.orchestrator.environment import (
|
|
OrchestratorEnvironment,
|
|
)
|
|
from openjarvis.learning.intelligence.orchestrator.types import OrchestratorAction
|
|
from openjarvis.tools._stubs import BaseTool, ToolSpec
|
|
|
|
# -- Mock tool ---------------------------------------------------------------
|
|
|
|
|
|
class _MockCalculator(BaseTool):
|
|
tool_id = "calculator"
|
|
|
|
@property
|
|
def spec(self) -> ToolSpec:
|
|
return ToolSpec(
|
|
name="calculator",
|
|
description="mock calculator",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"expression": {
|
|
"type": "string",
|
|
"description": "math expression",
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
def execute(self, **params) -> ToolResult:
|
|
expr = params.get("expression", "")
|
|
try:
|
|
result = str(eval(expr)) # noqa: S307
|
|
except Exception as e:
|
|
return ToolResult(
|
|
tool_name="calculator",
|
|
content=f"Error: {e}",
|
|
success=False,
|
|
)
|
|
return ToolResult(
|
|
tool_name="calculator",
|
|
content=result,
|
|
success=True,
|
|
)
|
|
|
|
|
|
# -- Tests -------------------------------------------------------------------
|
|
|
|
|
|
class TestOrchestratorEnvironment:
|
|
def test_reset_creates_clean_state(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
state = env.reset("What is 2+2?")
|
|
assert state.initial_prompt == "What is 2+2?"
|
|
assert state.num_turns() == 0
|
|
assert state.final_answer is None
|
|
|
|
def test_step_executes_tool(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
state = env.reset("q")
|
|
action = OrchestratorAction(
|
|
thought="calc",
|
|
tool_name="calculator",
|
|
tool_input="2+2",
|
|
)
|
|
state, obs = env.step(state, action)
|
|
assert state.num_turns() == 1
|
|
assert obs.latency_seconds >= 0
|
|
|
|
def test_is_done_on_final_answer(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
state = env.reset("q")
|
|
action = OrchestratorAction(
|
|
thought="done",
|
|
tool_name="calculator",
|
|
tool_input="2+2",
|
|
is_final_answer=True,
|
|
)
|
|
state, obs = env.step(state, action)
|
|
assert env.is_done(state) is True
|
|
|
|
def test_is_done_on_max_turns(self):
|
|
env = OrchestratorEnvironment(
|
|
tools=[_MockCalculator()], max_turns=2
|
|
)
|
|
state = env.reset("q")
|
|
for _ in range(2):
|
|
action = OrchestratorAction(
|
|
thought="go", tool_name="calculator", tool_input="1+1"
|
|
)
|
|
state, obs = env.step(state, action)
|
|
assert env.is_done(state) is True
|
|
|
|
def test_invalid_tool_raises(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
state = env.reset("q")
|
|
action = OrchestratorAction(
|
|
thought="t", tool_name="nonexistent", tool_input="x"
|
|
)
|
|
with pytest.raises(ValueError, match="not available"):
|
|
env.step(state, action)
|
|
|
|
def test_max_turns_exceeded_raises(self):
|
|
env = OrchestratorEnvironment(
|
|
tools=[_MockCalculator()], max_turns=1
|
|
)
|
|
state = env.reset("q")
|
|
action = OrchestratorAction(
|
|
thought="go", tool_name="calculator", tool_input="1+1"
|
|
)
|
|
state, _ = env.step(state, action)
|
|
with pytest.raises(ValueError, match="exceeded"):
|
|
env.step(state, action)
|
|
|
|
def test_get_available_tools(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
assert env.get_available_tools() == ["calculator"]
|
|
|
|
def test_not_done_initially(self):
|
|
env = OrchestratorEnvironment(tools=[_MockCalculator()])
|
|
state = env.reset("q")
|
|
assert env.is_done(state) is False
|