fix: fall back to agent CLI when OpenAI conversion fails - #147
fix: fall back to agent CLI when OpenAI conversion fails#147Chenxi1818 wants to merge 7 commits into
Conversation
convert_to_yaml() previously had only two paths: the OpenAI API, or a template-based conversion that drops paper citations, truncates the hypothesis to 500 chars, and keyword-guesses the domain. An OPENAI_API_KEY quota error therefore silently degraded every fetch. Add a middle path: on any OpenAI failure (quota, auth, missing key, unparseable response), retry the same prompt through the local agent CLI for --provider, which authenticates with its own login rather than OPENAI_API_KEY. The template converter remains as the last resort. - Split prompt construction into _build_conversion_prompt() so both LLM paths share one prompt, and the OpenAI call into _convert_with_openai() - Add _convert_with_cli(), mirroring the subprocess pattern in agents/manifest_trimmer.py - Add _extract_yaml() to strip CLI banners and markdown fences, and _parse_idea_yaml() to trim trailing agent chatter until the document parses and contains an idea key - Thread --provider through to the conversion step (was previously used only for repo naming and --run) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
_generate_repo_name called gpt-4o-mini (OpenRouter/OpenAI) to condense the title and silently fell back to the raw idea_id when no OPENROUTER_KEY/OPENAI_API_KEY was set or the call failed, producing names like probabilistic_calibration_metr_20260730_075410_ac649b07. Derive the slug deterministically from idea.yaml, tried in order title -> hypothesis -> domain, then the sanitized idea_id as last resort. Apostrophes are stripped so possessives stay one word. Threads hypothesis through create_research_repo and its two primary callers (runner, submit); fetch_from_ideahub is left to PR #147. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
save_yaml_file() mutated result['parsed'] to add source/source_url/author after convert_to_yaml() had already rendered 'yaml_string', so the written file and the dict handed to submit_idea() could disagree. Move the metadata into a _finalize() step inside convert_to_yaml() that applies provenance and re-renders the YAML from the same dict, and add a _dump_idea_yaml() helper that keeps multi-line text as literal blocks. Declare source/source_url in ideas/schema.yaml so the fields validate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merge 8e8d235 resolved by keeping both sides, splicing four blocks from main into the middle of this branch's rewritten conversion functions. The module has not been importable since: SyntaxError: expected 'except' or 'finally' block (line 584) Removed the four spliced blocks: - dead OpenRouter key check after `raise` in _parse_idea_yaml - OpenAI client construction inside _convert_with_cli, referencing names undefined in that scope (would NameError on every CLI success) - an except-less `try:` in convert_to_yaml holding main's inline OpenAI call, superseded by _convert_with_openai - a body-less `def save_yaml_file` and its duplicate definition Ported main's two real features onto this branch's structure, since both were left stranded in the dead fragments: - OpenRouter support, now in _resolve_api_key() + _convert_with_openai, mirroring src/cli/submit_local.py. OPENROUTER_KEY is the documented repo default and the live path had regressed to OPENAI_API_KEY only. - _drop_placeholder_author, now called from _finalize so both LLM paths get it. Simplified to mutate only the parsed dict, as _finalize re-renders the YAML anyway. It runs before _apply_source_metadata so a scraped author can fill a slot the model left as 'Unknown'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Frankbest18
left a comment
There was a problem hiding this comment.
Thanks for the great work! Here are some concern that hopefully can be addressed:
[P1] Converted YAML is accepted before it is validated as a complete NeuriCo idea
_parse_idea_yaml() considers any mapping containing an idea key successful. A syntactically valid but incomplete response—such as an idea containing only title—therefore prevents the CLI or template fallback from running.
NeuriCo has stronger validation in IdeaManager, but that validator is only reached when --submit is used, and it runs after the converted file has already been saved. Without --submit, an incomplete idea can be saved while the command reports success. This also covers unexpected YAML fields or provider commentary: the underlying issue is that conversion currently validates YAML syntax, not the resulting NeuriCo idea.
[P2] Model output can override authoritative provenance
source and source_url are described as auto-generated metadata, but _apply_source_metadata() uses setdefault(). If the model emits either field, its value is retained even though the converter already knows the actual source and fetched URL. This can silently save or submit incorrect provenance.
Trust-boundary note
I understand that IdeaHub is maintained internally, so the CLI-content concern depends on the project’s trust model. However, the current command accepts any URL beginning with http, and service ownership does not necessarily guarantee that every idea’s content is maintainer-authored. I would treat this as a documented trust-boundary concern rather than a confirmed blocker if the project can guarantee that all accepted content is intentionally trusted. Otherwise, defense-in-depth safeguards may be warranted to reduce the risk of prompt injection.
|
Thanks @Frankbest18 for the insightful review! I’ve addressed your feedback in the latest commit: [P1] Validation: Updated the fallback logic to validate the full NeuriCo idea schema (instead of just checking for the idea key). If the schema validation fails, it now properly falls through to the CLI / Template fallback paths. [P2] Metadata Override: Changed setdefault() to enforce/overwrite source and source_url with authoritative metadata, preventing any model-emitted provenance from overriding it. [Trust-boundary]: Appreciate the security note! I created a new issue #155 to mention this. Please let me know if everything looks good now! |
Frankbest18
left a comment
There was a problem hiding this comment.
Thanks for the update. There is only one remaining validation gap:
[P2] The shared validator does not enforce the full NeuriCo schema
validate_idea_spec() centralizes the existing manual validation, but it still accepts values that violate ideas/schema.yaml. For example, constraints: none is reported as valid even though constraints must be an object. That value is later treated as a mapping by the prompt generator, which can cause a runtime failure.
The validator similarly accepts invalid types for background and metadata, as well as strings shorter than the schema minimums. Consequently, the required-field fallback issue is fixed, but structurally invalid optional fields can still be saved and reported as a successful conversion.
Frankbest18
left a comment
There was a problem hiding this comment.
Thanks for the update. The previous validation concern is improved but not fully resolved.
[P2] Schema-invalid null metadata passes validation and fails during finalization
The validator treats an explicitly present null structured field as absent. For example:
idea:
title: A sufficiently long title
domain: machine_learning
hypothesis: This is a sufficiently long hypothesis.
metadata:This is reported as valid even though metadata, when present, must be an object according to ideas/schema.yaml. The converter then assumes that metadata is a mapping while applying authoritative provenance and raises:
AttributeError: 'NoneType' object has no attribute 'get'
This means a structurally invalid model response passes the stated validation boundary and only fails later during conversion finalization. The validator also continues to accept some schema-invalid nested values, such as non-string paper URLs and descriptions.
Issue
convert_to_yaml()previously had only two paths: the OpenAI API, or a template-based conversion that drops paper citations, truncates the hypothesis to 500 chars, and keyword-guesses the domain. AnOPENAI_API_KEYquota error therefore silently degraded every fetch.What's done
Added a middle path: on any OpenAI failure (quota, auth, missing key, unparseable response), retry the same prompt through the local agent CLI for
--provider, which authenticates with its own login rather thanOPENAI_API_KEY. The template converter remains as the last resort._build_conversion_prompt()so both LLM paths share one prompt, and isolated OpenAI API execution into_convert_with_openai()._convert_with_cli(), mirroring the subprocess pattern inagents/manifest_trimmer.py._extract_yaml()to strip CLI banners/markdown fences, and_parse_idea_yaml()to trim trailing agent chatter until the document successfully parses and contains anideakey.--providerthrough to the conversion step (previously used only for repo naming and--run).Behavior changes
--provideris omitted, the conversion fallback now usescodex._parse_idea_yaml()requires a dict containingidea, so bad output falls through to the next path instead of reachingsubmit_idea().