Files
wehub-resource-sync 2c632336aa
CI / Viewer CI (push) Successful in 13m37s
CI / Core CI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:38 +08:00

849 lines
30 KiB
Python

"""
Persistence helpers for local library records and materialized assets.
"""
from __future__ import annotations
import shutil
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Optional
from agent.compiler import (
_should_rewrite_visual_meshes_to_glb,
rewrite_visual_meshes_to_glb,
)
from agent.defaults import resolve_max_turns
from agent.prompts import resolve_system_prompt_path
from agent.run_context import (
SingleRunContext,
_build_single_run_context,
_default_model_id,
_detect_git_commit,
_detect_uv_lock_sha256,
_display_title,
_ensure_shared_system_prompt,
_first_string,
_platform_id,
_prompt_preview,
_resolve_runtime_record_author,
_sha256_file,
_sha256_text,
_utc_now,
)
from agent.tools import resolve_image_path as _resolve_image_path
from articraft.values import ProviderName
from storage.materialize import (
MaterializationStore,
build_compile_fingerprint_from_inputs,
ensure_record_artifacts_exist,
)
from storage.models import (
CompileReport as StorageCompileReport,
)
from storage.models import (
CompileWarning,
CreatorMetadata,
DisplayMetadata,
EnvironmentSettings,
GenerationSettings,
PromptingSettings,
Provenance,
Record,
RecordArtifacts,
RecordHashes,
RunSummary,
SdkSettings,
SourceRef,
)
from storage.records import RecordStore
from storage.repo import StorageRepo
from storage.revisions import (
active_inputs_dir,
build_revision_payload,
revision_artifacts_payload,
validate_revision_id,
)
from storage.trajectories import canonicalize_record_trace_dir
def _draft_model_template(*, sdk_package: str) -> str:
return f"""from __future__ import annotations
# Draft scaffold created by `articraft draft`.
# The target prompt for this record is stored in prompt.txt.
# Extend this scaffold with a valid Articraft model implementation.
import cadquery as cq
from {sdk_package} import ArticulatedObject, TestContext, TestReport, mesh_from_cadquery
def build_object_model() -> ArticulatedObject:
model = ArticulatedObject(name="draft_model")
return model
def run_tests() -> TestReport:
ctx = TestContext(object_model)
# `compile_model` automatically runs baseline sanity/QC:
# - `check_model_valid()`
# - exactly one root part
# - `check_mesh_assets_ready()`
# - disconnected floating-part-group detection
# - disconnected within-part geometry-island detection
# - current-pose real 3D overlap detection
# Use `run_tests()` only for prompt-specific exact checks, targeted poses,
# and explicit allowances such as `ctx.allow_overlap(...)`.
# If overlap QC reports an intersection, classify it first: intentional
# embeddings or nested fits should get a scoped allowance; unintended
# collisions should be fixed in geometry, support, mount, or pose.
# Encode the actual visual/mechanical claims with prompt-specific exact checks.
# Cover each applicable category before returning:
# - hero features are present and legible
# - mounted parts are connected/seated, not floating
# - important parts are in the right place
# - each new visible form or mechanism has a matching assertion
# Resolve exact Part / Articulation / named Visual objects once here, then
# pass those objects into ctx.expect_*, ctx.allow_*, and ctx.pose({{joint: value}}).
# For ctx.expect_* helpers, keep the first body/link arguments as Part objects.
# Named Visuals belong only in elem_a/elem_b/positive_elem/negative_elem/inner_elem/outer_elem.
# Prefer this object-first pattern over raw string test calls or global REFS bags.
# Example:
# lid = object_model.get_part("lid")
# body = object_model.get_part("body")
# lid_hinge = object_model.get_articulation("lid_hinge")
# hinge_leaf = lid.get_visual("hinge_leaf")
# body_leaf = body.get_visual("body_leaf")
# ctx.expect_overlap(lid, body, axes="xy", min_overlap=0.05)
# ctx.expect_gap(lid, body, axis="z", max_gap=0.001, max_penetration=0.0)
# ctx.expect_contact(lid, body, elem_a=hinge_leaf, elem_b=body_leaf)
# Keep pose-specific checks lean.
# Prefer a few decisive exact checks over broad heuristics.
# Add prompt-specific exact visual checks below; optional warning heuristics are not enough.
return ctx.report()
object_model = build_object_model()
"""
def _copy_if_exists(source: Path, destination: Path) -> None:
if not source.exists():
return
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
def _copytree_if_exists(source: Path, destination: Path) -> None:
if not source.exists():
return
destination.parent.mkdir(parents=True, exist_ok=True)
if destination.exists():
shutil.rmtree(destination)
shutil.copytree(source, destination)
def _replace_file_from_source(source: Path, destination: Path) -> None:
if destination.exists():
destination.unlink()
if source.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
def _replace_tree_from_source(source: Path, destination: Path) -> None:
if destination.exists():
shutil.rmtree(destination)
if source.exists():
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(source, destination)
def _remove_tree_if_exists(path: Path) -> None:
if path.exists():
shutil.rmtree(path)
def _normalize_materialization_asset_ref(filename: str) -> tuple[str, Path] | None:
raw = str(filename or "").strip()
if not raw:
return None
path = Path(raw)
if path.is_absolute() or ".." in path.parts:
return None
if raw.startswith("assets/meshes/"):
relative = Path(*path.parts[2:])
return ("meshes", relative) if relative.parts else None
if raw.startswith("meshes/"):
relative = Path(*path.parts[1:])
return ("meshes", relative) if relative.parts else None
if raw.startswith("assets/glb/"):
relative = Path(*path.parts[2:])
return ("glb", relative) if relative.parts else None
if raw.startswith("glb/"):
relative = Path(*path.parts[1:])
return ("glb", relative) if relative.parts else None
return None
def _referenced_materialization_assets(urdf_xml: str) -> dict[str, set[Path]]:
try:
root = ET.fromstring(urdf_xml)
except ET.ParseError as exc:
raise ValueError(f"Failed to parse persisted URDF for asset collection: {exc}") from exc
referenced: dict[str, set[Path]] = {"meshes": set(), "glb": set()}
for mesh_el in root.findall(".//mesh"):
filename = mesh_el.attrib.get("filename")
if not isinstance(filename, str):
continue
normalized = _normalize_materialization_asset_ref(filename)
if normalized is None:
continue
group, relative_path = normalized
referenced[group].add(relative_path)
return referenced
def _replace_selected_files_from_source(
source_root: Path,
destination_root: Path,
relative_paths: set[Path],
) -> None:
if destination_root.exists():
shutil.rmtree(destination_root)
if not relative_paths:
return
if not source_root.exists():
raise FileNotFoundError(f"Referenced asset source root is missing: {source_root}")
destination_root.parent.mkdir(parents=True, exist_ok=True)
for relative_path in sorted(relative_paths, key=lambda path: path.as_posix()):
source = source_root / relative_path
if not source.exists() or not source.is_file():
raise FileNotFoundError(f"Referenced asset is missing: {source}")
destination = destination_root / relative_path
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, destination)
def _normalize_prompt_kind(value: Any) -> str:
prompt_kind = str(value or "single_prompt")
if prompt_kind not in {"single_prompt", "prompt_series"}:
return "single_prompt"
return prompt_kind
def _build_record_display(
*,
existing_record: dict | None,
display_prompt: str,
label: str | None,
) -> DisplayMetadata:
if isinstance(existing_record, dict):
existing_display = existing_record.get("display")
if isinstance(existing_display, dict):
return DisplayMetadata(
title=_first_string(
existing_display.get("title"), _display_title(display_prompt, label=label)
),
prompt_preview=_first_string(
existing_display.get("prompt_preview"),
_prompt_preview(display_prompt),
),
)
return DisplayMetadata(
title=_display_title(display_prompt, label=label),
prompt_preview=_prompt_preview(display_prompt),
)
def _build_record_artifacts(
*,
revision_id: str,
has_cost_file: bool,
) -> RecordArtifacts:
artifacts = revision_artifacts_payload(revision_id=revision_id, has_cost_file=has_cost_file)
return RecordArtifacts(
prompt_txt=artifacts["prompt_txt"],
prompt_series_json=artifacts["prompt_series_json"],
model_py=str(artifacts["model_py"]),
provenance_json=str(artifacts["provenance_json"]),
cost_json=artifacts["cost_json"],
inputs_dir=artifacts["inputs_dir"],
traces_dir=artifacts["traces_dir"],
)
def _resolve_input_image_for_record(
storage_repo: StorageRepo,
*,
record_id: str,
provider: str,
) -> Path | None:
inputs_dir = active_inputs_dir(storage_repo, record_id)
if not inputs_dir.exists():
return None
files = sorted(path for path in inputs_dir.iterdir() if path.is_file())
if not files:
return None
if len(files) > 1:
raise ValueError(f"Record {record_id} has multiple input files; rerun supports one image.")
return _resolve_image_path(str(files[0]), provider=provider)
@dataclass(slots=True, frozen=True)
class SuccessRecordWrite:
repo_root: Path
storage_repo: StorageRepo
record_store: RecordStore
context: SingleRunContext
prompt_text: str
display_prompt: str
image_path: Path | None
provider: str
model_id: str
openai_transport: str
thinking_level: str
max_turns: int
system_prompt_path: Path
sdk_package: str
openai_reasoning_summary: str | None
max_cost_usd: float | None
final_code: str
urdf_xml: str
compile_warnings: list[str]
turn_count: int
tool_call_count: int
compile_attempt_count: int
label: str | None
tags: list[str]
category_slug: str | None
prompt_index: int | None = None
existing_record: dict | None = None
record_author: str | None = None
lineage: dict[str, Any] | None = None
revision_parent: dict[str, str] | None = None
revision_seed: dict[str, str] | None = None
inherited_inputs: list[dict[str, str]] | None = None
def create_draft_record(
*,
repo_root: Path,
prompt_text: str,
data_root: Path | None = None,
image_path: Path | None = None,
provider: str = "openai",
model_id: str | None = None,
openai_transport: str = "http",
thinking_level: str = "high",
max_turns: int | None = None,
system_prompt_path: str = "designer_system_prompt.txt",
sdk_package: str = "sdk",
openai_reasoning_summary: str | None = "auto",
max_cost_usd: float | None = None,
label: str | None = None,
tags: Optional[list[str]] = None,
record_id: str | None = None,
external_agent: str | None = None,
resolve_record_author_func: Callable[[Path], str | None] = _resolve_runtime_record_author,
) -> Path:
normalized_prompt = prompt_text.strip()
if not normalized_prompt:
raise ValueError("Prompt is required.")
resolved_repo_root = repo_root.resolve()
storage_repo = StorageRepo(resolved_repo_root, data_root=data_root)
storage_repo.ensure_layout()
record_author = resolve_record_author_func(resolved_repo_root)
record_store = RecordStore(storage_repo)
context = _build_single_run_context(
repo_root=resolved_repo_root,
prompt=normalized_prompt,
storage_repo=storage_repo,
record_id=record_id,
)
if storage_repo.layout.record_dir(context.record_id).exists():
raise ValueError(f"Record already exists: {context.record_id}")
if external_agent is not None:
selected_provider = provider
selected_model_id = model_id
selected_thinking_level = thinking_level
selected_openai_transport = None
selected_openai_reasoning_summary = None
resolved_max_turns = max_turns
system_prompt_file = "EXTERNAL_AGENT_DATA.md"
system_prompt_sha = None
else:
selected_provider = provider
selected_model_id = _default_model_id(
provider=provider,
model_id=model_id,
thinking_level=thinking_level,
openai_transport=openai_transport,
openai_reasoning_summary=openai_reasoning_summary,
)
selected_thinking_level = thinking_level
selected_openai_transport = (
openai_transport if selected_provider == ProviderName.OPENAI.value else None
)
selected_openai_reasoning_summary = (
openai_reasoning_summary if selected_provider == ProviderName.OPENAI.value else None
)
resolved_max_turns = resolve_max_turns(model_id=selected_model_id, max_turns=max_turns)
loaded_system_prompt_path = resolve_system_prompt_path(
system_prompt_path,
provider=provider,
sdk_package=sdk_package,
repo_root=resolved_repo_root,
)
system_prompt_file = loaded_system_prompt_path.name
system_prompt_sha = _ensure_shared_system_prompt(storage_repo, loaded_system_prompt_path)
record_store.ensure_record_dirs(context.record_id)
context.record_revision_dir.mkdir(parents=True, exist_ok=True)
storage_repo.write_text(context.record_prompt_path, normalized_prompt)
storage_repo.write_text(
context.record_model_path, _draft_model_template(sdk_package=sdk_package)
)
if image_path is not None:
record_store.copy_input_image(
context.record_id, image_path, revision_id=context.revision_id
)
prompt_sha = _sha256_text(normalized_prompt)
model_py_sha = _sha256_file(context.record_model_path)
run_summary = RunSummary(
turn_count=None if external_agent is not None else 0,
tool_call_count=None if external_agent is not None else 0,
compile_attempt_count=None if external_agent is not None else 0,
final_status="draft",
)
provenance = Provenance(
schema_version=2,
record_id=context.record_id,
generation=GenerationSettings(
provider=selected_provider,
model_id=selected_model_id,
thinking_level=selected_thinking_level,
openai_transport=selected_openai_transport,
openai_reasoning_summary=selected_openai_reasoning_summary,
max_turns=resolved_max_turns,
max_cost_usd=max_cost_usd,
),
prompting=PromptingSettings(
system_prompt_file=system_prompt_file,
system_prompt_sha256=system_prompt_sha,
),
sdk=SdkSettings(
sdk_package=sdk_package,
sdk_version="workspace",
sdk_fingerprint=None,
),
environment=EnvironmentSettings(
python_version=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
platform=_platform_id(),
git_commit=_detect_git_commit(resolved_repo_root),
uv_lock_sha256=_detect_uv_lock_sha256(resolved_repo_root),
),
run_summary=run_summary,
)
record_store.write_provenance(context.record_id, provenance, revision_id=context.revision_id)
artifacts_payload = revision_artifacts_payload(
revision_id=context.revision_id,
has_cost_file=False,
)
source_payload = SourceRef(run_id=None).to_dict()
generation_payload = GenerationSettings(
provider=selected_provider,
model_id=selected_model_id,
thinking_level=selected_thinking_level,
openai_transport=selected_openai_transport,
openai_reasoning_summary=selected_openai_reasoning_summary,
max_turns=resolved_max_turns,
max_cost_usd=max_cost_usd,
).to_dict()
run_summary_payload = run_summary.to_dict()
storage_repo.write_json(
storage_repo.layout.record_revision_metadata_path(context.record_id, context.revision_id),
build_revision_payload(
record_id=context.record_id,
revision_id=context.revision_id,
created_at=context.created_at,
prompt_text=normalized_prompt,
prompt_kind="single_prompt",
source=source_payload,
generation=generation_payload,
artifacts=artifacts_payload,
hashes={"prompt_sha256": prompt_sha, "model_py_sha256": model_py_sha},
run_summary=run_summary_payload,
),
)
record = Record(
schema_version=3,
record_id=context.record_id,
created_at=context.created_at,
updated_at=context.created_at,
rating=None,
kind="draft_model",
prompt_kind="single_prompt",
category_slug=None,
source=SourceRef(run_id=None),
sdk_package=sdk_package,
provider=selected_provider,
model_id=selected_model_id,
label=label,
tags=list(tags or []),
display=DisplayMetadata(
title=_display_title(normalized_prompt, label=label),
prompt_preview=_prompt_preview(normalized_prompt),
),
artifacts=RecordArtifacts(
prompt_txt=artifacts_payload["prompt_txt"],
prompt_series_json=None,
model_py=str(artifacts_payload["model_py"]),
provenance_json=str(artifacts_payload["provenance_json"]),
cost_json=None,
inputs_dir=artifacts_payload["inputs_dir"],
traces_dir=artifacts_payload["traces_dir"],
),
hashes=RecordHashes(
prompt_sha256=prompt_sha,
model_py_sha256=model_py_sha,
),
active_revision_id=context.revision_id,
lineage={
"origin_record_id": context.record_id,
"parent_record_id": None,
"parent_revision_id": None,
"edit_mode": "root",
},
creator=(
CreatorMetadata(
mode="external_agent",
agent=external_agent, # type: ignore[arg-type]
trace_available=False,
)
if external_agent is not None
else None
),
author=record_author,
)
record_store.write_record(record)
return context.record_dir
def write_success_record(
request: SuccessRecordWrite | None = None,
**kwargs: Any,
) -> Path:
if request is None:
request = SuccessRecordWrite(**kwargs)
elif kwargs:
raise TypeError("Pass either a SuccessRecordWrite request or keyword fields, not both.")
repo_root = request.repo_root
storage_repo = request.storage_repo
record_store = request.record_store
context = request.context
prompt_text = request.prompt_text
display_prompt = request.display_prompt
image_path = request.image_path
provider = request.provider
model_id = request.model_id
openai_transport = request.openai_transport
thinking_level = request.thinking_level
max_turns = request.max_turns
system_prompt_path = request.system_prompt_path
sdk_package = request.sdk_package
openai_reasoning_summary = request.openai_reasoning_summary
max_cost_usd = request.max_cost_usd
final_code = request.final_code
urdf_xml = request.urdf_xml
compile_warnings = request.compile_warnings
turn_count = request.turn_count
tool_call_count = request.tool_call_count
compile_attempt_count = request.compile_attempt_count
label = request.label
tags = request.tags
category_slug = request.category_slug
prompt_index = request.prompt_index
existing_record = request.existing_record
record_author = request.record_author
lineage = request.lineage
revision_parent = request.revision_parent
revision_seed = request.revision_seed
inherited_inputs = request.inherited_inputs or []
materializations = MaterializationStore(storage_repo)
persisted_warnings = list(compile_warnings)
persisted_urdf_xml = urdf_xml
if _should_rewrite_visual_meshes_to_glb(
sdk_package=sdk_package,
rewrite_visual_glb=None,
):
persisted_urdf_xml = rewrite_visual_meshes_to_glb(
urdf_xml,
sdk_package=sdk_package,
asset_root=context.staging_dir,
warnings=persisted_warnings,
)
revision_id = validate_revision_id(context.revision_id)
record_store.ensure_record_dirs(context.record_id)
context.record_revision_dir.mkdir(parents=True, exist_ok=True)
referenced_assets = _referenced_materialization_assets(persisted_urdf_xml)
storage_repo.write_text(context.record_prompt_path, prompt_text)
storage_repo.write_text(context.record_model_path, final_code)
storage_repo.write_text(context.record_urdf_path, persisted_urdf_xml)
system_prompt_sha = _ensure_shared_system_prompt(storage_repo, system_prompt_path)
for stale_file in ("model.urdf", "compile_report.json"):
stale_path = context.record_dir / stale_file
if stale_path.exists():
stale_path.unlink()
_remove_tree_if_exists(context.record_dir / "assets")
if image_path is not None:
record_store.copy_input_image(
context.record_id,
image_path,
missing_ok=True,
revision_id=revision_id,
)
_replace_file_from_source(context.cost_path, context.record_cost_path)
_replace_tree_from_source(
context.trace_dir,
context.record_trace_dir,
)
canonicalize_record_trace_dir(storage_repo, context.record_id, revision_id=revision_id)
if context.trace_dir.exists():
shutil.rmtree(context.trace_dir)
_replace_selected_files_from_source(
context.staging_dir / "assets" / "meshes",
storage_repo.layout.record_materialization_asset_meshes_dir(context.record_id),
referenced_assets["meshes"],
)
_replace_selected_files_from_source(
context.staging_dir / "assets" / "glb",
storage_repo.layout.record_materialization_asset_glb_dir(context.record_id),
referenced_assets["glb"],
)
_replace_tree_from_source(
context.staging_dir / "assets" / "viewer",
storage_repo.layout.record_materialization_asset_viewer_dir(context.record_id),
)
prompt_sha = _sha256_text(prompt_text)
model_py_sha = _sha256_file(context.record_model_path)
fingerprint_inputs = {
"model_py_sha256": model_py_sha,
"sdk_fingerprint": None,
}
compile_report = StorageCompileReport(
schema_version=1,
record_id=context.record_id,
status="success",
urdf_path="model.urdf",
warnings=[
CompileWarning(code="warning", message=warning) for warning in persisted_warnings
],
checks_run=["compile_urdf"],
metrics={
"compile_level": "full",
"turn_count": turn_count,
"tool_call_count": tool_call_count,
"compile_attempt_count": compile_attempt_count,
"active_revision_id": revision_id,
"fingerprint_inputs": fingerprint_inputs,
"materialization_fingerprint": build_compile_fingerprint_from_inputs(
fingerprint_inputs
),
},
)
materializations.write_compile_report(context.record_id, compile_report)
provenance = Provenance(
schema_version=2,
record_id=context.record_id,
generation=GenerationSettings(
provider=provider,
model_id=model_id,
thinking_level=thinking_level,
openai_transport=openai_transport if provider == ProviderName.OPENAI.value else None,
openai_reasoning_summary=(
openai_reasoning_summary if provider == ProviderName.OPENAI.value else None
),
max_turns=max_turns,
max_cost_usd=max_cost_usd,
),
prompting=PromptingSettings(
system_prompt_file=system_prompt_path.name,
system_prompt_sha256=system_prompt_sha,
),
sdk=SdkSettings(
sdk_package=sdk_package,
sdk_version="workspace",
sdk_fingerprint=None,
),
environment=EnvironmentSettings(
python_version=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
platform=_platform_id(),
git_commit=_detect_git_commit(repo_root),
uv_lock_sha256=_detect_uv_lock_sha256(repo_root),
),
run_summary=RunSummary(
turn_count=turn_count,
tool_call_count=tool_call_count,
compile_attempt_count=compile_attempt_count,
final_status="success",
),
)
record_store.write_provenance(context.record_id, provenance, revision_id=revision_id)
source_payload = SourceRef(
run_id=context.run_id,
prompt_index=prompt_index,
).to_dict()
generation_payload = GenerationSettings(
provider=provider,
model_id=model_id,
thinking_level=thinking_level,
openai_transport=openai_transport if provider == ProviderName.OPENAI.value else None,
openai_reasoning_summary=(
openai_reasoning_summary if provider == ProviderName.OPENAI.value else None
),
max_turns=max_turns,
max_cost_usd=max_cost_usd,
).to_dict()
run_summary_payload = RunSummary(
turn_count=turn_count,
tool_call_count=tool_call_count,
compile_attempt_count=compile_attempt_count,
final_status="success",
).to_dict()
artifacts_payload = revision_artifacts_payload(
revision_id=revision_id,
has_cost_file=context.record_cost_path.exists(),
)
storage_repo.write_json(
storage_repo.layout.record_revision_metadata_path(context.record_id, revision_id),
build_revision_payload(
record_id=context.record_id,
revision_id=revision_id,
created_at=context.created_at,
prompt_text=prompt_text,
prompt_kind=(
_normalize_prompt_kind(existing_record.get("prompt_kind"))
if isinstance(existing_record, dict)
else "single_prompt"
),
source=source_payload,
generation=generation_payload,
artifacts=artifacts_payload,
hashes={"prompt_sha256": prompt_sha, "model_py_sha256": model_py_sha},
run_summary=run_summary_payload,
parent=revision_parent,
seed=revision_seed,
inherited_inputs=inherited_inputs,
),
)
if lineage is None:
if isinstance(existing_record, dict) and isinstance(existing_record.get("lineage"), dict):
lineage = dict(existing_record["lineage"])
else:
lineage = {
"origin_record_id": context.record_id,
"parent_record_id": None,
"parent_revision_id": None,
"edit_mode": "root",
}
record = Record(
schema_version=3,
record_id=context.record_id,
created_at=(
_first_string(existing_record.get("created_at"), context.created_at)
if isinstance(existing_record, dict)
else context.created_at
),
updated_at=_utc_now(),
rating=(existing_record.get("rating") if isinstance(existing_record, dict) else None),
secondary_rating=(
existing_record.get("secondary_rating") if isinstance(existing_record, dict) else None
),
kind=(
_first_string(existing_record.get("kind"), "generated_model")
if isinstance(existing_record, dict)
else "generated_model"
),
prompt_kind=(
_normalize_prompt_kind(existing_record.get("prompt_kind"))
if isinstance(existing_record, dict)
else "single_prompt"
),
category_slug=category_slug,
source=SourceRef(
run_id=context.run_id,
prompt_index=prompt_index,
),
sdk_package=sdk_package,
provider=provider,
model_id=model_id,
label=label,
tags=tags,
display=_build_record_display(
existing_record=existing_record,
display_prompt=display_prompt,
label=label,
),
artifacts=_build_record_artifacts(
revision_id=revision_id,
has_cost_file=context.record_cost_path.exists(),
),
hashes=RecordHashes(
prompt_sha256=prompt_sha,
model_py_sha256=model_py_sha,
),
active_revision_id=revision_id,
lineage=lineage,
author=(
str(existing_record.get("author") or "").strip()
if isinstance(existing_record, dict)
else None
)
or record_author,
rated_by=(
str(existing_record.get("rated_by") or "").strip()
if isinstance(existing_record, dict)
else None
)
or None,
secondary_rated_by=(
str(existing_record.get("secondary_rated_by") or "").strip()
if isinstance(existing_record, dict)
else None
)
or None,
)
record_store.write_record(record)
ensure_record_artifacts_exist(
storage_repo,
context.record_id,
required=("model_py", "provenance_json"),
)
return context.record_dir