Skip to content

Commit 644eb79

Browse files
committed
more fixes
1 parent d0f58fa commit 644eb79

File tree

5 files changed

+352
-23
lines changed

5 files changed

+352
-23
lines changed

eval_protocol/auth.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,7 @@ def get_fireworks_account_id() -> Optional[str]:
186186
return account_id_from_file
187187
account_id = os.environ.get("FIREWORKS_ACCOUNT_ID")
188188
if account_id:
189-
logger.debug(
190-
"Using FIREWORKS_ACCOUNT_ID from environment variable (profile active but file missing)."
191-
)
189+
logger.debug("Using FIREWORKS_ACCOUNT_ID from environment variable (profile active but file missing).")
192190
return account_id
193191
else:
194192
# Default behavior: env overrides file

eval_protocol/cli.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,21 +425,27 @@ def _extract_flag_value(argv_list, flag_name):
425425

426426
if args.command == "preview":
427427
from .cli_commands.preview import preview_command
428+
428429
return preview_command(args)
429430
elif args.command == "deploy":
430431
from .cli_commands.deploy import deploy_command
432+
431433
return deploy_command(args)
432434
elif args.command == "deploy-mcp":
433435
from .cli_commands.deploy_mcp import deploy_mcp_command
436+
434437
return deploy_mcp_command(args)
435438
elif args.command == "agent-eval":
436439
from .cli_commands.agent_eval_cmd import agent_eval_command
440+
437441
return agent_eval_command(args)
438442
elif args.command == "logs":
439443
from .cli_commands.logs import logs_command
444+
440445
return logs_command(args)
441446
elif args.command == "upload":
442447
from .cli_commands.upload import upload_command
448+
443449
return upload_command(args)
444450
elif args.command == "run":
445451
# For the 'run' command, Hydra takes over argument parsing.
@@ -496,6 +502,7 @@ def _extract_flag_value(argv_list, flag_name):
496502

497503
try:
498504
from .cli_commands.run_eval_cmd import hydra_cli_entry_point
505+
499506
hydra_entry = cast(Any, hydra_cli_entry_point)
500507
hydra_entry() # type: ignore # pylint: disable=no-value-for-parameter
501508
return 0

eval_protocol/cli_commands/upload.py

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -179,22 +179,85 @@ def _discover_tests(root: str) -> list[DiscoveredTest]:
179179
return sorted(by_qual.values(), key=lambda x: (x.file_path, x.lineno or 0))
180180

181181

182+
def _to_pyargs_nodeid(file_path: str, func_name: str) -> str | None:
183+
"""Attempt to build a pytest nodeid suitable for `pytest <nodeid>`.
184+
185+
Preference order:
186+
1) Dotted package module path with double-colon: pkg.subpkg.module::func
187+
2) Filesystem path with double-colon: path/to/module.py::func
188+
189+
Returns dotted form when package root can be inferred (directory chain with __init__.py
190+
leading up to a directory contained in sys.path). Returns None if no reasonable
191+
nodeid can be created (should be rare).
192+
"""
193+
try:
194+
abs_path = os.path.abspath(file_path)
195+
dir_path, filename = os.path.split(abs_path)
196+
module_base, ext = os.path.splitext(filename)
197+
if ext != ".py":
198+
# Not a python file
199+
return None
200+
201+
# Walk up while packages have __init__.py
202+
segments: list[str] = [module_base]
203+
current = dir_path
204+
package_root = None
205+
while True:
206+
if os.path.isfile(os.path.join(current, "__init__.py")):
207+
segments.insert(0, os.path.basename(current))
208+
parent = os.path.dirname(current)
209+
# Stop if parent is not within current sys.path import roots
210+
if parent == current:
211+
break
212+
current = parent
213+
else:
214+
package_root = current
215+
break
216+
217+
# If we found a package chain, check that the package_root is importable (in sys.path)
218+
if package_root and any(
219+
os.path.abspath(sp).rstrip(os.sep) == os.path.abspath(package_root).rstrip(os.sep) for sp in sys.path
220+
):
221+
dotted = ".".join(segments)
222+
return f"{dotted}::{func_name}"
223+
224+
# Do not emit a dotted top-level module for non-packages; prefer path-based nodeid
225+
226+
# Fallback to relative path (if under cwd) or absolute path
227+
cwd = os.getcwd()
228+
try:
229+
rel = os.path.relpath(abs_path, cwd)
230+
except Exception:
231+
rel = abs_path
232+
return f"{rel}::{func_name}"
233+
except Exception:
234+
return None
235+
236+
182237
def _parse_entry(entry: str, cwd: str) -> tuple[str, str]:
183-
# Accept module:function or path::function
238+
# Accept module::function, path::function, or legacy module:function
184239
entry = entry.strip()
185240
if "::" in entry:
186-
path_part, func = entry.split("::", 1)
187-
abs_path = os.path.abspath(os.path.join(cwd, path_part))
188-
module_name = Path(abs_path).stem
189-
return abs_path, func
241+
target, func = entry.split("::", 1)
242+
# Determine if target looks like a filesystem path; otherwise treat as module path
243+
looks_like_path = (
244+
"/" in target or "\\" in target or target.endswith(".py") or os.path.exists(os.path.join(cwd, target))
245+
)
246+
if looks_like_path:
247+
abs_path = os.path.abspath(os.path.join(cwd, target))
248+
return abs_path, func
249+
else:
250+
# Treat as module path for --pyargs style
251+
return target, func
190252
elif ":" in entry:
253+
# Legacy support: module:function → convert to module path + function
191254
module, func = entry.split(":", 1)
192255
return module, func
193256
else:
194-
raise ValueError("--entry must be in 'module:function' or 'path::function' format")
257+
raise ValueError("--entry must be in 'module::function', 'path::function', or 'module:function' format")
195258

196259

197-
def _generate_ts_mode_code_from_entry(entry: str, cwd: str) -> tuple[str, str, str]:
260+
def _generate_ts_mode_code_from_entry(entry: str, cwd: str) -> tuple[str, str, str, str]:
198261
target, func = _parse_entry(entry, cwd)
199262

200263
# Check if target looks like a file path
@@ -217,10 +280,12 @@ def _generate_ts_mode_code_from_entry(entry: str, cwd: str) -> tuple[str, str, s
217280
sys.modules[spec.name] = module
218281
spec.loader.exec_module(module) # type: ignore[attr-defined]
219282
module_name = spec.name
283+
source_file_path = target
220284
else:
221285
# Treat as module path (e.g., "my_package.my_module")
222286
module_name = target
223287
module = importlib.import_module(module_name)
288+
source_file_path = getattr(module, "__file__", "") or ""
224289

225290
if not hasattr(module, func):
226291
raise ValueError(f"Function '{func}' not found in module '{module_name}'")
@@ -238,7 +303,7 @@ def _generate_ts_mode_code_from_entry(entry: str, cwd: str) -> tuple[str, str, s
238303
nodeids=[],
239304
)
240305
)
241-
return code, file_name, qualname
306+
return code, file_name, qualname, os.path.abspath(source_file_path) if source_file_path else ""
242307

243308

244309
def _generate_ts_mode_code(test: DiscoveredTest) -> tuple[str, str]:
@@ -440,10 +505,8 @@ def upload_command(args: argparse.Namespace) -> int:
440505
entries = [e.strip() for e in re.split(r"[,\s]+", entries_arg) if e.strip()]
441506
selected_specs: list[tuple[str, str, str, str]] = []
442507
for e in entries:
443-
code, file_name, qualname = _generate_ts_mode_code_from_entry(e, root)
444-
# For --entry mode, extract file path from the entry
445-
file_path = e.split("::")[0] if "::" in e else ""
446-
selected_specs.append((code, file_name, qualname, file_path))
508+
code, file_name, qualname, resolved_path = _generate_ts_mode_code_from_entry(e, root)
509+
selected_specs.append((code, file_name, qualname, resolved_path))
447510
else:
448511
print("Scanning for evaluation tests...")
449512
tests = _discover_tests(root)
@@ -496,15 +559,21 @@ def upload_command(args: argparse.Namespace) -> int:
496559
# Normalize the evaluator ID to meet Fireworks requirements
497560
evaluator_id = _normalize_evaluator_id(evaluator_id)
498561

499-
# Compute entry point metadata for backend: prefer module:function, fallback to path::function
562+
# Compute entry point metadata for backend as a pytest nodeid usable with `pytest <entrypoint>`
563+
# Always prefer a path-based nodeid to work in plain pytest environments (server may not use --pyargs)
500564
func_name = qualname.split(".")[-1]
501-
module_part = qualname.rsplit(".", 1)[0] if "." in qualname else ""
502-
# Use pytest pyargs style: package.module:function
503-
if module_part and "." in module_part:
504-
entry_point = f"{module_part}:{func_name}"
565+
entry_point = None
566+
if source_file_path:
567+
# Use path relative to current working directory if possible
568+
abs_path = os.path.abspath(source_file_path)
569+
try:
570+
rel = os.path.relpath(abs_path, root)
571+
except Exception:
572+
rel = abs_path
573+
entry_point = f"{rel}::{func_name}"
505574
else:
506-
# If we cannot derive a dotted module path, don't set entry point
507-
entry_point = None
575+
# Fallback: use filename from qualname only (rare)
576+
entry_point = f"{func_name}.py::{func_name}"
508577

509578
print(f"\nUploading evaluator '{evaluator_id}' for {qualname.split('.')[-1]}...")
510579
try:
@@ -524,7 +593,27 @@ def upload_command(args: argparse.Namespace) -> int:
524593
# Print success message with Fireworks dashboard link
525594
print(f"\n✅ Successfully uploaded evaluator: {evaluator_id}")
526595
print("📊 View in Fireworks Dashboard:")
527-
print(f" https://app.fireworks.ai/dashboard/evaluators/{evaluator_id}")
596+
# Map API base to app host (e.g., dev.api.fireworks.ai -> dev.app.fireworks.ai)
597+
from urllib.parse import urlparse
598+
599+
api_base = os.environ.get("FIREWORKS_API_BASE", "https://api.fireworks.ai")
600+
try:
601+
parsed = urlparse(api_base)
602+
host = parsed.netloc or parsed.path # handle cases where scheme may be missing
603+
# Mapping rules:
604+
# - dev.api.fireworks.ai → dev.fireworks.ai
605+
# - *.api.fireworks.ai → *.app.fireworks.ai (default)
606+
if host.startswith("dev.api.fireworks.ai"):
607+
app_host = "dev.fireworks.ai"
608+
elif host.startswith("api."):
609+
app_host = host.replace("api.", "app.", 1)
610+
else:
611+
app_host = host
612+
scheme = parsed.scheme or "https"
613+
dashboard_url = f"{scheme}://{app_host}/dashboard/evaluators/{evaluator_id}"
614+
except Exception:
615+
dashboard_url = f"https://app.fireworks.ai/dashboard/evaluators/{evaluator_id}"
616+
print(f" {dashboard_url}")
528617
print()
529618
except Exception as e:
530619
print(f"Failed to upload {qualname}: {e}")

eval_protocol/evaluation.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,14 @@ def create(self, evaluator_id, display_name=None, description=None, force=False)
536536
# Include optional entry point when provided
537537
if self.entry_point:
538538
payload_data["evaluator"]["entryPoint"] = self.entry_point
539+
logger.info(f"Including entryPoint in payload: {self.entry_point}")
540+
541+
# Debug log the create payload structure (without sample data)
542+
try:
543+
logger.info(f"Create API Request Payload: {json.dumps(payload_data, indent=2)}")
544+
except Exception:
545+
# If serialization fails for any reason, skip debug dump
546+
pass
539547

540548
if "dev.api.fireworks.ai" in self.api_base and account_id == "fireworks":
541549
account_id = "pyroworks-dev"

0 commit comments

Comments
 (0)