style: fix all Ruff lint errors in src/

Shorten docstrings in backward-compat shims to fix E501 (line too long).
Sort imports in learning/__init__.py. Zero Ruff violations remaining.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-03-06 23:28:57 +00:00
co-authored by Claude Opus 4.6
parent 1aae88ccde
commit 3677dbab6c
31 changed files with 1339 additions and 37 deletions
+1
View File
@@ -43,6 +43,7 @@ class _OpenAICompatibleEngine(InferenceEngine):
"temperature": temperature,
"max_tokens": max_tokens,
"stream": False,
"chat_template_kwargs": {"enable_thinking": False},
**kwargs,
}
try:
+82 -2
View File
@@ -230,12 +230,54 @@ class CloudEngine(InferenceEngine):
"ANTHROPIC_API_KEY and install "
"openjarvis[inference-cloud]"
)
# Separate system message from conversation messages
# Separate system message and convert to Anthropic message format
system_text = ""
chat_msgs: List[Dict[str, Any]] = []
for m in messages:
if m.role.value == "system":
system_text = m.content
elif m.role.value == "tool":
# Anthropic expects tool results as role="user" with
# tool_result content blocks
tool_result_block = {
"type": "tool_result",
"tool_use_id": m.tool_call_id or "",
"content": m.content,
}
# Merge consecutive tool results into a single user message
if (
chat_msgs
and chat_msgs[-1]["role"] == "user"
and isinstance(chat_msgs[-1]["content"], list)
and chat_msgs[-1]["content"]
and chat_msgs[-1]["content"][-1].get("type") == "tool_result"
):
chat_msgs[-1]["content"].append(tool_result_block)
else:
chat_msgs.append({
"role": "user",
"content": [tool_result_block],
})
elif m.role.value == "assistant" and m.tool_calls:
# Convert assistant messages with tool_calls to Anthropic
# content blocks (text + tool_use)
content_blocks: List[Dict[str, Any]] = []
if m.content:
content_blocks.append({"type": "text", "text": m.content})
for tc in m.tool_calls:
args = tc.arguments
if isinstance(args, str):
try:
args = json.loads(args)
except (json.JSONDecodeError, TypeError):
args = {"input": args}
content_blocks.append({
"type": "tool_use",
"id": tc.id,
"name": tc.name,
"input": args if isinstance(args, dict) else {},
})
chat_msgs.append({"role": "assistant", "content": content_blocks})
else:
chat_msgs.append({"role": m.role.value, "content": m.content})
create_kwargs: Dict[str, Any] = {
@@ -308,12 +350,50 @@ class CloudEngine(InferenceEngine):
"GEMINI_API_KEY or GOOGLE_API_KEY and install "
"openjarvis[inference-google]"
)
# Build contents from messages
# Build contents from messages, converting tool roles for Gemini
system_text = ""
contents: List[Dict[str, Any]] = []
for m in messages:
if m.role.value == "system":
system_text = m.content
elif m.role.value == "tool":
# Gemini expects function responses as role="user" with
# function_response parts
fn_resp_part = {
"function_response": {
"name": m.name or "unknown",
"response": {"result": m.content},
}
}
# Merge consecutive tool results into a single user message
if (
contents
and contents[-1]["role"] == "user"
and contents[-1]["parts"]
and "function_response" in contents[-1]["parts"][-1]
):
contents[-1]["parts"].append(fn_resp_part)
else:
contents.append({"role": "user", "parts": [fn_resp_part]})
elif m.role.value == "assistant" and m.tool_calls:
# Convert assistant tool_calls to function_call parts
parts: List[Dict[str, Any]] = []
if m.content:
parts.append({"text": m.content})
for tc in m.tool_calls:
args = tc.arguments
if isinstance(args, str):
try:
args = json.loads(args)
except (json.JSONDecodeError, TypeError):
args = {"input": args}
parts.append({
"function_call": {
"name": tc.name,
"args": args if isinstance(args, dict) else {},
}
})
contents.append({"role": "model", "parts": parts})
elif m.role.value == "assistant":
contents.append({"role": "model", "parts": [{"text": m.content}]})
else:
@@ -0,0 +1,42 @@
# DeepPlanning evaluation with MonitorOperativeAgent
# Benchmark: long-horizon planning with hard constraints (travel + shopping)
# Paper: arXiv:2601.18137, best frontier: GPT-5.2-high at 44.6%
[meta]
name = "deepplanning-monitor-operative"
description = "DeepPlanning constraint satisfaction with MonitorOperativeAgent"
[defaults]
temperature = 0.6
max_tokens = 32768
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 1024
[run]
max_workers = 1
output_dir = "results/deepplanning-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "Qwen/Qwen3.5-35B-A3B"
engine = "vllm"
param_count_b = 35.0
active_params_b = 3.0
gpu_peak_tflops = 989.5
gpu_peak_bandwidth_gb_s = 3350.0
num_gpus = 2
[[benchmarks]]
name = "deepplanning"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 50
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "memory_store", "memory_search",
]
@@ -0,0 +1,74 @@
# MonitorOperativeAgent evaluation on long-horizon benchmarks
# Machine: 8x H100-80GB, engine: vLLM, model: GLM-5 (Q4_K_M GGUF)
# Inference: GLM-5 reasoning mode
# Benchmarks: LogHub, LifelongAgentBench, DeepPlanning
[meta]
name = "glm5-q4km-monitor-operative"
description = "Evaluate MonitorOperativeAgent with GLM-5 Q4_K_M on long-horizon monitoring benchmarks"
[defaults]
temperature = 0.6
max_tokens = 32768
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 4096
[run]
max_workers = 1
output_dir = "results/glm5-q4km-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "GLM-5-Q4_K_M"
engine = "vllm"
param_count_b = 744.0
active_params_b = 40.0
gpu_peak_tflops = 989.5
gpu_peak_bandwidth_gb_s = 3350.0
num_gpus = 8
# --- Benchmarks ---
[[benchmarks]]
name = "loghub"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
[[benchmarks]]
name = "lifelong-agent"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
[[benchmarks]]
name = "deepplanning"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
@@ -0,0 +1,74 @@
# MonitorOperativeAgent evaluation on long-horizon benchmarks
# Machine: 8x H100-80GB, engine: vLLM, model: Kimi-K2.5 (IQ4_XS GGUF)
# Inference: Kimi-K2.5 thinking mode (temperature=1.0, top_p=0.95)
# Benchmarks: LogHub, LifelongAgentBench, DeepPlanning
[meta]
name = "kimi-k2.5-monitor-operative"
description = "Evaluate MonitorOperativeAgent with Kimi-K2.5 on long-horizon monitoring benchmarks"
[defaults]
temperature = 1.0 # Kimi-K2.5 thinking mode recommended
max_tokens = 32768
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 4096
[run]
max_workers = 1
output_dir = "results/kimi-k2.5-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "Kimi-K2.5-IQ4_XS"
engine = "vllm"
param_count_b = 1000.0
active_params_b = 32.0
gpu_peak_tflops = 989.5
gpu_peak_bandwidth_gb_s = 3350.0
num_gpus = 8
# --- Benchmarks ---
[[benchmarks]]
name = "loghub"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
[[benchmarks]]
name = "lifelong-agent"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
[[benchmarks]]
name = "deepplanning"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 250
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
@@ -0,0 +1,42 @@
# PaperArena evaluation with MonitorOperativeAgent
# Benchmark: scientific literature reasoning (MC/CA/OA)
# Paper: arXiv:2510.10909, best frontier: Gemini 2.5 Pro at 38.8%
[meta]
name = "paperarena-monitor-operative"
description = "PaperArena scientific reasoning with MonitorOperativeAgent"
[defaults]
temperature = 0.6
max_tokens = 32768
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 1024
[run]
max_workers = 1
output_dir = "results/paperarena-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "Qwen/Qwen3.5-35B-A3B"
engine = "vllm"
param_count_b = 35.0
active_params_b = 3.0
gpu_peak_tflops = 989.5
gpu_peak_bandwidth_gb_s = 3350.0
num_gpus = 2
[[benchmarks]]
name = "paperarena"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 50
tools = [
"think", "calculator", "code_interpreter",
"web_search", "file_read", "memory_store", "memory_search",
]
@@ -0,0 +1,57 @@
# MonitorOperativeAgent evaluation on Phase 25 text-to-text benchmarks
# Machine: 2x H100-80GB (GPUs 6-7), engine: vLLM, model: Qwen3.5-35B-A3B
# Inference: Qwen3.5 thinking mode (temperature=0.6, top_p=0.95, top_k=20)
# Benchmarks: LogHub (log anomaly detection), LifelongAgentBench (sequential SQL)
# Note: AMA-Bench excluded (no public dataset yet, paper arxiv:2602.22769)
# DeepPlanning/PaperArena: see dedicated config files
[meta]
name = "qwen3.5-35b-monitor-operative"
description = "Evaluate MonitorOperativeAgent on Phase 25 text-to-text benchmarks"
[defaults]
temperature = 0.6 # Qwen3.5 thinking mode (precise analysis)
max_tokens = 32768 # Qwen3.5 recommended for general queries
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 1024
[run]
max_workers = 1 # Sequential for agent state safety
output_dir = "results/qwen3.5-35b-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "Qwen/Qwen3.5-35B-A3B"
engine = "vllm"
param_count_b = 35.0
active_params_b = 3.0
gpu_peak_tflops = 989.5 # H100 SXM FP16 peak TFLOPS
gpu_peak_bandwidth_gb_s = 3350.0 # H100 SXM memory bandwidth
num_gpus = 2
# --- Phase 25 Text-to-Text Benchmarks ---
[[benchmarks]]
name = "loghub"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 50
tools = [
"think", "calculator", "code_interpreter",
"memory_store", "memory_search", "memory_retrieve",
]
[[benchmarks]]
name = "lifelong-agent"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 50
tools = [
"think", "calculator", "code_interpreter",
"memory_store", "memory_search", "memory_retrieve",
]
@@ -0,0 +1,50 @@
# MonitorOperativeAgent evaluation on long-horizon benchmarks
# Machine: 4x H100-80GB (GPUs 0-3), engine: vLLM, model: Qwen3.5-122B-A10B-FP8
# Inference: Qwen3.5 non-thinking mode (temperature=0.6)
# Benchmarks: LifelongAgentBench only (LogHub done at 94%, DeepPlanning needs >32K ctx)
# Note: PaperArena excluded (HF repo Melmaphother/PaperArena-Data is empty)
# AMA-Bench excluded (no public dataset yet, paper arxiv:2602.22769)
[meta]
name = "qwen3.5-122b-monitor-operative"
description = "Evaluate MonitorOperativeAgent on long-horizon monitoring benchmarks"
[defaults]
temperature = 0.6 # Qwen3.5 thinking mode recommended
max_tokens = 1024 # Keep short to prevent hangs; answers are brief
[judge]
model = "gpt-5-mini-2025-08-07"
temperature = 0.0
max_tokens = 4096
[run]
max_workers = 1 # Sequential for episode mode; 1 worker for agent state safety
output_dir = "results/qwen3.5-122b-monitor-operative/"
seed = 42
telemetry = true
gpu_metrics = true
[[models]]
name = "Qwen/Qwen3.5-122B-A10B-FP8"
engine = "vllm"
param_count_b = 122.0
active_params_b = 10.0
gpu_peak_tflops = 989.5 # H100 SXM FP16 peak TFLOPS
gpu_peak_bandwidth_gb_s = 3350.0 # H100 SXM memory bandwidth
num_gpus = 4
# --- Benchmarks ---
[[benchmarks]]
name = "lifelong-agent"
backend = "jarvis-agent"
agent = "monitor_operative"
max_samples = 50
tools = [
"think", "calculator", "code_interpreter",
"shell_exec", "file_read",
"memory_store", "memory_search", "memory_retrieve",
"kg_add_entity", "kg_add_relation", "kg_query", "kg_neighbors",
"db_query", "web_search", "http_request",
]
@@ -27,26 +27,32 @@ telemetry = true
[[models]]
name = "claude-opus-4-6"
engine = "cloud"
provider = "anthropic"
[[models]]
name = "claude-sonnet-4-6"
engine = "cloud"
provider = "anthropic"
[[models]]
name = "gpt-5.4"
engine = "cloud"
provider = "openai"
[[models]]
name = "gpt-5-mini"
engine = "cloud"
provider = "openai"
[[models]]
name = "gemini-3.1-pro-preview"
engine = "cloud"
provider = "google"
[[models]]
name = "gemini-3-flash-preview"
engine = "cloud"
provider = "google"
# --- Benchmarks ---
@@ -27,26 +27,32 @@ telemetry = true
[[models]]
name = "claude-opus-4-6"
engine = "cloud"
provider = "anthropic"
[[models]]
name = "claude-sonnet-4-6"
engine = "cloud"
provider = "anthropic"
[[models]]
name = "gpt-5.4"
engine = "cloud"
provider = "openai"
[[models]]
name = "gpt-5-mini"
engine = "cloud"
provider = "openai"
[[models]]
name = "gemini-3.1-pro-preview"
engine = "cloud"
provider = "google"
[[models]]
name = "gemini-3-flash-preview"
engine = "cloud"
provider = "google"
# --- Benchmarks ---
@@ -38,7 +38,7 @@ active_params_b = 17.0
num_gpus = 8
[[models]]
name = "unsloth/Qwen3.5-122B-A10B-GGUF"
name = "Qwen/Qwen3.5-122B-A10B-FP8"
engine = "vllm"
param_count_b = 122.0
active_params_b = 10.0
@@ -38,7 +38,7 @@ active_params_b = 17.0
num_gpus = 8
[[models]]
name = "unsloth/Qwen3.5-122B-A10B-GGUF"
name = "Qwen/Qwen3.5-122B-A10B-FP8"
engine = "vllm"
param_count_b = 122.0
active_params_b = 10.0
@@ -0,0 +1,238 @@
"""DeepPlanning: long-horizon planning with constraints.
Evaluates agents on complex shopping tasks with hard constraints
(product attributes, ratings, stock, shipping).
Source: https://huggingface.co/datasets/Qwen/DeepPlanning
Paper: arXiv:2601.18137
The dataset contains shopping planning tasks at 3 difficulty levels
(120 total cases). Each case has a natural-language query, product
catalog, and ground-truth product selections with constraint metadata.
"""
from __future__ import annotations
import json
import logging
import random
import tarfile
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional
from openjarvis.evals.core.dataset import DatasetProvider
from openjarvis.evals.core.types import EvalRecord
logger = logging.getLogger(__name__)
_SYSTEM_PROMPT = (
"You are a shopping assistant. Given the user's request and a product catalog, "
"select the correct products that satisfy ALL stated constraints.\n\n"
"IMPORTANT INSTRUCTIONS:\n"
"- Read through the product catalog data directly — do NOT write code to parse it\n"
"- For each constraint, find products matching ALL requirements\n"
"- If a constraint references data not in the catalog (e.g., transport time "
"when only shipping provider is listed), use reasonable inference from "
"available shipping info\n"
"- For each selected product, state: name, brand, price, and the specific "
"attribute values that match each constraint\n"
"- Present your final answer as a clear list of selected products"
)
class DeepPlanningDataset(DatasetProvider):
"""DeepPlanning long-horizon planning benchmark.
Extracts shopping planning tasks from Qwen/DeepPlanning tar.gz archives.
Each case has a query with constraints and ground-truth product selections.
"""
dataset_id = "deepplanning"
dataset_name = "DeepPlanning"
def __init__(
self,
cache_dir: Optional[str] = None,
) -> None:
self._cache_dir = cache_dir
self._records: List[EvalRecord] = []
def load(
self,
*,
max_samples: Optional[int] = None,
split: Optional[str] = None,
seed: Optional[int] = None,
) -> None:
snapshot_dir = self._find_snapshot_dir()
if snapshot_dir is None:
self._download_dataset()
snapshot_dir = self._find_snapshot_dir()
if snapshot_dir is None:
logger.error("Failed to download DeepPlanning dataset")
return
records: List[EvalRecord] = []
for level in [1, 2, 3]:
tar_path = snapshot_dir / f"database_level{level}.tar.gz"
if not tar_path.exists():
logger.warning("Missing %s", tar_path)
continue
level_records = self._extract_cases(tar_path, level)
records.extend(level_records)
logger.info("Loaded %d DeepPlanning cases", len(records))
if seed is not None:
random.Random(seed).shuffle(records)
if max_samples is not None:
records = records[:max_samples]
self._records = records
def iter_records(self) -> Iterable[EvalRecord]:
return iter(self._records)
def size(self) -> int:
return len(self._records)
def _find_snapshot_dir(self) -> Optional[Path]:
"""Find the HF cache snapshot directory for Qwen/DeepPlanning."""
base = Path.home() / ".cache" / "huggingface" / "hub"
ds_dir = base / "datasets--Qwen--DeepPlanning" / "snapshots"
if not ds_dir.exists():
return None
snapshots = list(ds_dir.iterdir())
return snapshots[0] if snapshots else None
def _download_dataset(self) -> None:
"""Trigger HF datasets download to populate the cache."""
try:
from datasets import load_dataset
except ImportError:
raise ImportError(
"datasets required for DeepPlanning. "
"Install with: pip install datasets"
)
logger.info("Downloading Qwen/DeepPlanning from HuggingFace...")
# Loading triggers the download even though we don't use the result
load_dataset("Qwen/DeepPlanning", split="train")
def _extract_cases(
self, tar_path: Path, level: int,
) -> List[EvalRecord]:
"""Extract shopping cases from a tar.gz archive."""
records: List[EvalRecord] = []
try:
with tarfile.open(tar_path, "r:gz") as tf:
# Build index of all members by directory
members_by_dir: Dict[str, Dict[str, Any]] = {}
for m in tf.getmembers():
parts = Path(m.name).parts
if len(parts) >= 2:
case_dir = parts[1] # e.g. "case_12"
fname = parts[-1]
members_by_dir.setdefault(case_dir, {})[fname] = m
for case_name, files in members_by_dir.items():
val_member = files.get("validation_cases.json")
prod_member = files.get("products.jsonl")
if val_member is None:
continue
f = tf.extractfile(val_member)
if f is None:
continue
data = json.loads(f.read().decode("utf-8"))
# Load product catalog
products_text = ""
if prod_member is not None:
pf = tf.extractfile(prod_member)
if pf is not None:
products_text = pf.read().decode("utf-8").strip()
rec = self._case_to_record(
data, level, case_name, products_text,
)
if rec:
records.append(rec)
except Exception:
logger.warning("Failed to read %s", tar_path, exc_info=True)
return records
def _case_to_record(
self,
data: Dict[str, Any],
level: int,
case_name: str,
products_text: str = "",
) -> Optional[EvalRecord]:
"""Convert a validation_cases.json to an EvalRecord."""
query = data.get("query", "")
if not query:
return None
ground_truth = data.get("ground_truth_products", [])
meta_info = data.get("meta_info", [])
# Build reference as structured summary
ref_parts = []
for prod in ground_truth:
name = prod.get("name", "")
brand = prod.get("brand", "")
price = prod.get("price", "")
ref_parts.append(f"{brand} {name} (${price})")
reference = "; ".join(ref_parts) if ref_parts else ""
# Count constraints
n_constraints = sum(
len(m.get("features", [])) for m in meta_info
)
difficulty = (
"easy" if level == 1
else "medium" if level == 2
else "hard"
)
# Build product catalog section
catalog_section = ""
if products_text:
catalog_section = (
f"## Product Catalog\n"
f"Below is the product database in JSONL format "
f"(one JSON object per line):\n\n"
f"```jsonl\n{products_text}\n```\n\n"
)
problem = (
f"{_SYSTEM_PROMPT}\n\n"
f"## Shopping Request\n{query}\n\n"
f"{catalog_section}"
f"## Required Output\n"
f"List each product that satisfies ALL constraints. For each product:\n"
f"1. Product name and brand\n"
f"2. Price\n"
f"3. For each constraint, the matching attribute value from the catalog"
)
return EvalRecord(
record_id=f"deepplanning-L{level}-{case_name}",
problem=problem,
reference=reference,
category="agentic",
subject=f"shopping_L{level}",
metadata={
"task_type": "shopping",
"level": level,
"difficulty": difficulty,
"case_name": case_name,
"n_products": len(ground_truth),
"n_constraints": n_constraints,
"ground_truth_products": ground_truth,
"meta_info": meta_info,
},
)
__all__ = ["DeepPlanningDataset"]
+225 -13
View File
@@ -24,6 +24,12 @@ _SYSTEM_PROMPT = (
"Previous tasks in this session may have modified the environment."
)
_KG_SYSTEM = (
"You are a knowledge graph reasoning agent. Answer the question by "
"analyzing the provided knowledge graph operations and entity information. "
"Reason through each step and provide the final answer directly."
)
class LifelongAgentDataset(DatasetProvider):
"""LifelongAgentBench sequential task learning benchmark."""
@@ -56,19 +62,27 @@ class LifelongAgentDataset(DatasetProvider):
if not data_dir.exists():
self._download(data_dir)
task_sequences = self._load_task_sequences(data_dir)
# Try Parquet first (HuggingFace format), then JSON/JSONL
records = self._load_from_parquet(data_dir)
if not records:
task_sequences = self._load_task_sequences(data_dir)
if seed is not None:
random.Random(seed).shuffle(task_sequences)
if max_samples is not None:
task_sequences = task_sequences[:max_samples]
self._episodes = []
self._records = []
for seq in task_sequences:
episode = self._sequence_to_episode(seq)
self._episodes.append(episode)
self._records.extend(episode)
return
if seed is not None:
random.Random(seed).shuffle(task_sequences)
random.Random(seed).shuffle(records)
if max_samples is not None:
task_sequences = task_sequences[:max_samples]
self._episodes = []
self._records = []
for seq in task_sequences:
episode = self._sequence_to_episode(seq)
self._episodes.append(episode)
self._records.extend(episode)
records = records[:max_samples]
self._records = records
def iter_records(self) -> Iterable[EvalRecord]:
return iter(self._records)
@@ -89,15 +103,214 @@ class LifelongAgentDataset(DatasetProvider):
) from exc
data_dir.mkdir(parents=True, exist_ok=True)
snapshot_download(
repo_id="LifelongAgentBench/LifelongAgentBench",
repo_id="csyq/LifelongAgentBench",
repo_type="dataset",
local_dir=str(data_dir),
)
def _load_from_parquet(self, data_dir: Path) -> List[EvalRecord]:
"""Load records from Parquet files (HuggingFace dataset format)."""
parquet_files = list(data_dir.rglob("*.parquet"))
if not parquet_files:
return []
try:
import pandas as pd
except ImportError:
logger.warning("pandas required for Parquet loading")
return []
records: List[EvalRecord] = []
for pf in sorted(parquet_files):
subset_name = pf.parent.name
df = pd.read_parquet(pf)
logger.info("Loading %d rows from %s", len(df), pf)
for _, row in df.iterrows():
row_dict = row.to_dict()
if subset_name == "knowledge_graph":
rec = self._kg_row_to_record(row_dict)
elif subset_name == "db_bench":
rec = self._db_row_to_record(row_dict)
elif subset_name == "os_interaction":
rec = self._os_row_to_record(row_dict)
else:
rec = self._generic_row_to_record(row_dict, subset_name)
if rec is not None:
records.append(rec)
return records
def _kg_row_to_record(self, row: Dict[str, Any]) -> Optional[EvalRecord]:
question = row.get("question", "")
if not question:
return None
qid = row.get("qid", row.get("sample_index", "unknown"))
entity_dict = row.get("entity_dict", {})
if isinstance(entity_dict, str):
try:
entity_dict = json.loads(entity_dict)
except (json.JSONDecodeError, TypeError):
entity_dict = {}
entity_desc = ""
if isinstance(entity_dict, dict) and entity_dict:
entities = "\n".join(
f" - {name}: {mid}" for name, mid in entity_dict.items()
)
entity_desc = f"Entities:\n{entities}"
action_list = row.get("action_list", [])
action_desc = ""
if action_list:
if isinstance(action_list, str):
try:
action_list = json.loads(action_list)
except (json.JSONDecodeError, TypeError):
action_list = []
if isinstance(action_list, list) and action_list:
steps = "\n".join(f" {i+1}. {a}" for i, a in enumerate(action_list))
action_desc = f"\n\nKG Operations:\n{steps}"
problem = (
f"{_KG_SYSTEM}\n\n"
f"{entity_desc}"
f"{action_desc}\n\n"
f"Question: {question}"
)
answer_list = row.get("answer_list", [])
if isinstance(answer_list, str):
try:
answer_list = json.loads(answer_list)
except (json.JSONDecodeError, TypeError):
answer_list = [answer_list]
reference = ", ".join(str(a) for a in answer_list) if answer_list else ""
return EvalRecord(
record_id=f"lifelong-kg-{qid}",
problem=problem,
reference=reference,
category="agentic",
subject="knowledge_graph",
metadata={
"subset": "knowledge_graph",
"qid": qid,
"action_list": action_list,
"skill_list": row.get("skill_list", []),
},
)
def _db_row_to_record(self, row: Dict[str, Any]) -> Optional[EvalRecord]:
instruction = row.get("instruction", "")
if not instruction:
return None
idx = row.get("sample_index", "unknown")
table_info = row.get("table_info", {})
if isinstance(table_info, str):
try:
table_info = json.loads(table_info)
except (json.JSONDecodeError, TypeError):
table_info = {}
table_ctx = ""
if isinstance(table_info, dict) and table_info:
tname = table_info.get("name", "unknown")
cols = table_info.get("column_info_list", [])
if cols:
col_desc = ", ".join(
f"{c.get('name', '?')} ({c.get('type', '?')})" for c in cols
)
table_ctx = f"\n\nTable: {tname}\nColumns: {col_desc}"
problem = f"{_SYSTEM_PROMPT}\n\n## Task\n{instruction}{table_ctx}"
answer_info = row.get("answer_info", {})
if isinstance(answer_info, str):
try:
answer_info = json.loads(answer_info)
except (json.JSONDecodeError, TypeError):
answer_info = {}
reference = ""
if isinstance(answer_info, dict):
if answer_info.get("direct") is not None:
reference = str(answer_info["direct"])
elif answer_info.get("sql"):
reference = answer_info["sql"]
elif answer_info.get("md5"):
reference = answer_info["md5"]
return EvalRecord(
record_id=f"lifelong-db-{idx}",
problem=problem,
reference=reference,
category="agentic",
subject="database",
metadata={
"subset": "db_bench",
"sample_index": idx,
"skill_list": row.get("skill_list", []),
},
)
def _os_row_to_record(self, row: Dict[str, Any]) -> Optional[EvalRecord]:
instruction = row.get("instruction", "")
if not instruction:
return None
idx = row.get("sample_index", "unknown")
problem = f"{_SYSTEM_PROMPT}\n\n## Task\n{instruction}"
eval_info = row.get("evaluation_info", {})
if isinstance(eval_info, str):
try:
eval_info = json.loads(eval_info)
except (json.JSONDecodeError, TypeError):
eval_info = {}
reference = ""
if isinstance(eval_info, dict):
cmd_item = eval_info.get("evaluation_command_item", {})
if isinstance(cmd_item, dict):
reference = cmd_item.get("script", "")
return EvalRecord(
record_id=f"lifelong-os-{idx}",
problem=problem,
reference=reference,
category="agentic",
subject="os",
metadata={
"subset": "os_interaction",
"sample_index": idx,
"skill_list": row.get("skill_list", []),
},
)
def _generic_row_to_record(
self, row: Dict[str, Any], subset_name: str,
) -> Optional[EvalRecord]:
instruction = row.get("instruction", row.get("question", row.get("task", "")))
if not instruction:
return None
idx = row.get("sample_index", row.get("id", "unknown"))
expected = row.get("answer", row.get("expected_output", ""))
return EvalRecord(
record_id=f"lifelong-{subset_name}-{idx}",
problem=f"{_SYSTEM_PROMPT}\n\n## Task\n{instruction}",
reference=str(expected),
category="agentic",
subject=subset_name,
metadata={"subset": subset_name, "sample_index": idx},
)
def _load_task_sequences(
self, data_dir: Path,
) -> List[List[Dict[str, Any]]]:
"""Load task sequences from disk."""
"""Load task sequences from JSON/JSONL files (legacy format)."""
sequences: List[List[Dict[str, Any]]] = []
for p in sorted(data_dir.rglob("*.json")):
@@ -105,7 +318,6 @@ class LifelongAgentDataset(DatasetProvider):
with open(p) as f:
data = json.load(f)
if isinstance(data, list):
# Could be a sequence of tasks or list of sequences
if data and isinstance(data[0], list):
sequences.extend(data)
elif data and isinstance(data[0], dict):
+170
View File
@@ -0,0 +1,170 @@
"""PaperArena: scientific literature reasoning benchmark.
Evaluates agents on research paper comprehension with three question types:
MC (multiple choice), CA (closed answer), OA (open answer) across
easy/medium/hard difficulty.
Source: https://github.com/Melmaphother/PaperArena
Paper: arXiv:2510.10909
"""
from __future__ import annotations
import json
import logging
import random
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional
from openjarvis.evals.core.dataset import DatasetProvider
from openjarvis.evals.core.types import EvalRecord
logger = logging.getLogger(__name__)
_SYSTEM_PROMPT = (
"You are a scientific research assistant. Read the paper context carefully "
"and answer the question. For multiple-choice questions, respond with just "
"the letter (A, B, C, or D). For other questions, provide a precise answer."
)
class PaperArenaDataset(DatasetProvider):
"""PaperArena scientific literature reasoning benchmark.
Three question types (MC, CA, OA) across three difficulty levels.
"""
dataset_id = "paperarena"
dataset_name = "PaperArena"
def __init__(
self,
cache_dir: Optional[str] = None,
) -> None:
self._cache_dir = (
Path(cache_dir) if cache_dir
else Path.home() / ".cache" / "paperarena"
)
self._records: List[EvalRecord] = []
def load(
self,
*,
max_samples: Optional[int] = None,
split: Optional[str] = None,
seed: Optional[int] = None,
) -> None:
data_dir = self._cache_dir / "qa"
if not data_dir.exists():
self._download()
records = self._load_records(data_dir)
if seed is not None:
random.Random(seed).shuffle(records)
if max_samples is not None:
records = records[:max_samples]
self._records = records
def iter_records(self) -> Iterable[EvalRecord]:
return iter(self._records)
def size(self) -> int:
return len(self._records)
def _download(self) -> None:
"""Download PaperArena data from HuggingFace or GitHub."""
try:
from huggingface_hub import snapshot_download
except ImportError as exc:
raise ImportError(
"huggingface_hub required for PaperArena. "
"Install with: pip install huggingface_hub"
) from exc
self._cache_dir.mkdir(parents=True, exist_ok=True)
logger.info("Downloading PaperArena dataset...")
snapshot_download(
repo_id="Melmaphother/PaperArena-Data",
repo_type="dataset",
local_dir=str(self._cache_dir),
)
def _load_records(self, data_dir: Path) -> List[EvalRecord]:
"""Load QA records from JSON/JSONL files."""
records: List[EvalRecord] = []
for p in sorted(data_dir.rglob("*.json")):
try:
with open(p) as f:
data = json.load(f)
items = data if isinstance(data, list) else [data]
for item in items:
rec = self._item_to_record(item)
if rec is not None:
records.append(rec)
except (json.JSONDecodeError, OSError):
logger.debug("Skipping: %s", p)
for p in sorted(data_dir.rglob("*.jsonl")):
try:
with open(p) as f:
for line in f:
line = line.strip()
if line:
item = json.loads(line)
rec = self._item_to_record(item)
if rec is not None:
records.append(rec)
except (json.JSONDecodeError, OSError):
logger.debug("Skipping: %s", p)
return records
def _item_to_record(self, item: Dict[str, Any]) -> Optional[EvalRecord]:
"""Convert a raw QA item to an EvalRecord."""
question_id = item.get("question_id", item.get("id", ""))
question_type = item.get("question_type", item.get("type", "OA")).upper()
difficulty = item.get("difficulty", "medium").lower()
question = item.get("question", "")
context = item.get("context", item.get("paper_context", ""))
reference = item.get("answer", item.get("reference", ""))
options = item.get("options", None)
tool_chain = item.get("minimal_tool_chain", [])
if not question:
return None
# Build problem prompt
prompt = f"{_SYSTEM_PROMPT}\n\n"
if context:
prompt += f"## Paper Context\n{context}\n\n"
prompt += f"## Question\n{question}"
if options and question_type == "MC":
options_text = "\n".join(
f" {k}) {v}" for k, v in options.items()
) if isinstance(options, dict) else "\n".join(
f" {chr(65 + i)}) {o}" for i, o in enumerate(options)
)
prompt += f"\n\nOptions:\n{options_text}"
subject = f"{difficulty}_{question_type.lower()}"
return EvalRecord(
record_id=f"paperarena-{question_id}",
problem=prompt,
reference=str(reference),
category="agentic",
subject=subject,
metadata={
"question_id": question_id,
"question_type": question_type,
"difficulty": difficulty,
"minimal_tool_chain": tool_chain,
},
)
__all__ = ["PaperArenaDataset"]
@@ -0,0 +1,87 @@
"""LLM-judge scorer for DeepPlanning constraint satisfaction."""
from __future__ import annotations
import re
from typing import Any, Dict, Optional, Tuple
from openjarvis.evals.core.scorer import LLMJudgeScorer
from openjarvis.evals.core.types import EvalRecord
_JUDGE_PROMPT = """You are evaluating a planning task response.
## Task Type: {task_type}
## Original Task
{problem}
## Reference Plan
{reference}
## Agent's Plan
{model_answer}
Evaluate whether the agent's plan satisfies ALL constraints from the task.
For travel planning, check:
- Route consistency and feasibility
- Time feasibility (business hours, travel times)
- Budget accuracy (costs within stated limits)
- Personalization requirements met
For shopping tasks, check:
- Correct products selected
- Coupon/discount rules applied correctly
- Cart total accuracy
Respond with exactly: CORRECT or INCORRECT
Then provide a brief explanation."""
class DeepPlanningScorer(LLMJudgeScorer):
"""Score DeepPlanning responses via LLM judge on constraint satisfaction."""
scorer_id = "deepplanning"
def score(
self, record: EvalRecord, model_answer: str,
) -> Tuple[Optional[bool], Dict[str, Any]]:
if not model_answer or not model_answer.strip():
return False, {"reason": "empty_response"}
if not record.reference or not record.reference.strip():
return None, {"reason": "no_ground_truth"}
task_type = record.metadata.get("task_type", "planning")
# Extract problem without system prompt prefix
problem = record.problem
if "## Task" in problem:
problem = problem.split("## Task", 1)[-1]
prompt = _JUDGE_PROMPT.format(
task_type=task_type,
problem=problem,
reference=record.reference,
model_answer=model_answer,
)
try:
raw = self._ask_judge(prompt, max_tokens=4096)
is_correct = bool(re.search(r"\bCORRECT\b", raw, re.IGNORECASE))
if re.search(r"\bINCORRECT\b", raw, re.IGNORECASE):
is_correct = False
return is_correct, {
"match_type": "llm_judge",
"raw_judge_output": raw,
"task_type": task_type,
}
except Exception as exc:
return False, {
"match_type": "llm_judge_error",
"error": str(exc),
}
__all__ = ["DeepPlanningScorer"]
@@ -0,0 +1,153 @@
"""Scorer for PaperArena: MC exact match + LLM judge for CA/OA."""
from __future__ import annotations
import re
from typing import Any, Dict, Optional, Tuple
from openjarvis.evals.core.scorer import LLMJudgeScorer
from openjarvis.evals.core.types import EvalRecord
_JUDGE_PROMPT = """You are evaluating a scientific question answer.
## Question
{question}
## Reference Answer
{reference}
## Agent's Answer
{model_answer}
Is the agent's answer correct? Consider semantic equivalence, not exact wording.
For numerical answers, accept reasonable rounding.
Respond with exactly: CORRECT or INCORRECT"""
class PaperArenaScorer(LLMJudgeScorer):
"""Score PaperArena: MC via letter extraction, CA/OA via LLM judge."""
scorer_id = "paperarena"
def score(
self, record: EvalRecord, model_answer: str,
) -> Tuple[Optional[bool], Dict[str, Any]]:
if not model_answer or not model_answer.strip():
return False, {"reason": "empty_response"}
if not record.reference or not record.reference.strip():
return None, {"reason": "no_ground_truth"}
question_type = record.metadata.get("question_type", "OA").upper()
if question_type == "MC":
return self._score_mc(record, model_answer)
else:
return self._score_open(record, model_answer)
def _score_mc(
self, record: EvalRecord, model_answer: str,
) -> Tuple[Optional[bool], Dict[str, Any]]:
"""Score multiple-choice via letter extraction."""
ref_letter = record.reference.strip().upper()
if len(ref_letter) != 1 or ref_letter not in "ABCD":
# Reference is not a single letter; fall back to judge
return self._score_open(record, model_answer)
# Try regex extraction for answer letter
extracted = self._extract_letter(model_answer)
if extracted is None:
# Fall back to LLM extraction
extracted = self._extract_letter_with_llm(
record.problem, model_answer,
)
if extracted is None:
return None, {
"match_type": "exact_letter",
"reason": "no_letter_extracted",
}
is_correct = extracted == ref_letter
return is_correct, {
"match_type": "exact_letter",
"reference_letter": ref_letter,
"candidate_letter": extracted,
}
def _extract_letter(self, text: str) -> Optional[str]:
"""Try to extract a single answer letter from text via regex."""
# Common patterns: "The answer is A", "A)", "(A)", just "A"
patterns = [
r"(?:the\s+answer\s+is|answer:?)\s*([A-D])\b",
r"\b([A-D])\)",
r"\(([A-D])\)",
]
for pat in patterns:
m = re.search(pat, text, re.IGNORECASE)
if m:
return m.group(1).upper()
# Last single capital letter on its own line
lines = text.strip().splitlines()
for line in reversed(lines):
stripped = line.strip()
if len(stripped) == 1 and stripped.upper() in "ABCD":
return stripped.upper()
return None
def _extract_letter_with_llm(
self, problem: str, model_answer: str,
) -> Optional[str]:
"""Use LLM to extract answer letter."""
prompt = (
f"Extract the final answer letter (A, B, C, or D) from this response.\n"
f"Problem: {problem[:2000]}\n"
f"Response: {model_answer[:2000]}\n\n"
f"Return ONLY a single letter (A-D), or NONE if no answer found."
)
try:
raw = self._ask_judge(prompt, max_tokens=4096)
letter = raw.strip().upper()
m = re.search(r"([A-D])", letter)
if m:
return m.group(1)
except Exception:
pass
return None
def _score_open(
self, record: EvalRecord, model_answer: str,
) -> Tuple[Optional[bool], Dict[str, Any]]:
"""Score CA/OA via LLM judge."""
question = record.problem
if "## Question" in question:
question = question.split("## Question")[-1].strip()
prompt = _JUDGE_PROMPT.format(
question=question,
reference=record.reference,
model_answer=model_answer,
)
try:
raw = self._ask_judge(prompt, max_tokens=4096)
is_correct = bool(re.search(r"\bCORRECT\b", raw, re.IGNORECASE))
if re.search(r"\bINCORRECT\b", raw, re.IGNORECASE):
is_correct = False
return is_correct, {
"match_type": "llm_judge",
"raw_judge_output": raw,
"question_type": record.metadata.get("question_type", "OA"),
}
except Exception as exc:
return False, {
"match_type": "llm_judge_error",
"error": str(exc),
}
__all__ = ["PaperArenaScorer"]
+3 -3
View File
@@ -11,13 +11,13 @@ from openjarvis.learning._stubs import (
from openjarvis.learning.agent_evolver import AgentConfigEvolver
from openjarvis.learning.heuristic_reward import HeuristicRewardFunction
from openjarvis.learning.learning_orchestrator import LearningOrchestrator
from openjarvis.learning.optimize.llm_optimizer import LLMOptimizer
from openjarvis.learning.optimize.optimizer import OptimizationEngine
from openjarvis.learning.optimize.store import OptimizationStore
from openjarvis.learning.router import (
HeuristicRouter,
build_routing_context,
)
from openjarvis.learning.optimize.llm_optimizer import LLMOptimizer
from openjarvis.learning.optimize.optimizer import OptimizationEngine
from openjarvis.learning.optimize.store import OptimizationStore
from openjarvis.learning.training.data import TrainingDataMiner
from openjarvis.learning.training.lora import HAS_TORCH, LoRATrainer, LoRATrainingConfig
+8 -2
View File
@@ -6,8 +6,14 @@ from openjarvis.learning.optimize.config import (
load_optimize_config,
)
from openjarvis.learning.optimize.llm_optimizer import LLMOptimizer
from openjarvis.learning.optimize.optimizer import OptimizationEngine, compute_pareto_frontier
from openjarvis.learning.optimize.search_space import DEFAULT_SEARCH_SPACE, build_search_space
from openjarvis.learning.optimize.optimizer import (
OptimizationEngine,
compute_pareto_frontier,
)
from openjarvis.learning.optimize.search_space import (
DEFAULT_SEARCH_SPACE,
build_search_space,
)
from openjarvis.learning.optimize.store import OptimizationStore
from openjarvis.learning.optimize.trial_runner import (
BenchmarkSpec,
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.feedback moved to learning.optimize.feedback."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.feedback import * # noqa: F401,F403
from openjarvis.learning.optimize.feedback import __all__ # noqa: F401
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.feedback.collector moved to learning.optimize.feedback.collector."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.feedback.collector import * # noqa: F401,F403
from openjarvis.learning.optimize.feedback.collector import __all__ # noqa: F401
+5 -3
View File
@@ -1,4 +1,6 @@
"""Backward-compatibility shim -- optimize.feedback.judge moved to learning.optimize.feedback.judge."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.feedback.judge import * # noqa: F401,F403
from openjarvis.learning.optimize.feedback.judge import __all__ # noqa: F401
from openjarvis.learning.optimize.feedback.judge import _parse_score # noqa: F401
from openjarvis.learning.optimize.feedback.judge import (
__all__, # noqa: F401
_parse_score, # noqa: F401
)
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.llm_optimizer moved to learning.optimize.llm_optimizer."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.llm_optimizer import * # noqa: F401,F403
from openjarvis.learning.optimize.llm_optimizer import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.optimizer moved to learning.optimize.optimizer."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.optimizer import * # noqa: F401,F403
from openjarvis.learning.optimize.optimizer import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.personal moved to learning.optimize.personal."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.personal import * # noqa: F401,F403
from openjarvis.learning.optimize.personal import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.personal.dataset moved to learning.optimize.personal.dataset."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.personal.dataset import * # noqa: F401,F403
from openjarvis.learning.optimize.personal.dataset import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.personal.scorer moved to learning.optimize.personal.scorer."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.personal.scorer import * # noqa: F401,F403
from openjarvis.learning.optimize.personal.scorer import __all__ # noqa: F401
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.personal.synthesizer moved to learning.optimize.personal.synthesizer."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.personal.synthesizer import * # noqa: F401,F403
from openjarvis.learning.optimize.personal.synthesizer import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.search_space moved to learning.optimize.search_space."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.search_space import * # noqa: F401,F403
from openjarvis.learning.optimize.search_space import __all__ # noqa: F401
+1 -1
View File
@@ -1,3 +1,3 @@
"""Backward-compatibility shim -- optimize.trial_runner moved to learning.optimize.trial_runner."""
"""Backward-compat shim: moved to learning.optimize."""
from openjarvis.learning.optimize.trial_runner import * # noqa: F401,F403
from openjarvis.learning.optimize.trial_runner import __all__ # noqa: F401
+4 -2
View File
@@ -1,4 +1,6 @@
"""Backward-compatibility shim -- optimize.types moved to learning.optimize.types."""
from openjarvis.learning.optimize.types import * # noqa: F401,F403
from openjarvis.learning.optimize.types import __all__ # noqa: F401
from openjarvis.learning.optimize.types import _PARAM_TO_RECIPE # noqa: F401
from openjarvis.learning.optimize.types import (
_PARAM_TO_RECIPE, # noqa: F401
__all__, # noqa: F401
)