{"record":{"id":"015c25666dea67f8","repo":"FoundationAgents/MetaGPT","slug":"use-review-after-fill","errorCode":null,"errorMessage":"use `review` after `fill`","messagePattern":"use `review` after `fill`","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"metagpt/actions/action_node.py","lineNumber":737,"sourceCode":"        # generate review comments\n        if review_mode == ReviewMode.HUMAN:\n            review_comments = await self.human_review()\n        else:\n            review_comments = await self.auto_review()\n\n        if not review_comments:\n            logger.warning(\"There are no review comments\")\n        return review_comments\n\n    async def review(self, strgy: str = \"simple\", review_mode: ReviewMode = ReviewMode.AUTO):\n        \"\"\"only give the review comment of each exist and mismatch key\n\n        :param strgy: simple/complex\n         - simple: run only once\n         - complex: run each node\n        \"\"\"\n        if not hasattr(self, \"llm\"):\n            raise RuntimeError(\"use `review` after `fill`\")\n        assert review_mode in ReviewMode\n        assert self.instruct_content, 'review only support with `schema != \"raw\"`'\n\n        if strgy == \"simple\":\n            review_comments = await self.simple_review(review_mode)\n        elif strgy == \"complex\":\n            # review each child node one-by-one\n            review_comments = {}\n            for _, child in self.children.items():\n                child_review_comment = await child.simple_review(review_mode)\n                review_comments.update(child_review_comment)\n\n        return review_comments\n\n    async def human_revise(self) -> dict[str, str]:\n        review_contents = HumanInteraction().interact_with_instruct_content(\n            instruct_content=self.instruct_content, mapping=self.get_mapping(mode=\"auto\"), interact_type=\"revise\"\n        )","sourceCodeStart":719,"sourceCodeEnd":755,"githubUrl":"https://github.com/FoundationAgents/MetaGPT/blob/11cdf466d042aece04fc6cfd13b28e1a70341b1f/metagpt/actions/action_node.py#L719-L755","documentation":"YamlModelWithoutDefault is a Pydantic base class meant to reject configuration files that still contain template placeholders. Its mode='before' model_validator scans the incoming values container for any element containing the substring 'YOUR' and raises ValueError before field validation. In this checkout the class is not used by Config itself (Config inherits YamlModel), so the error comes from a subclass of YamlModelWithoutDefault or equivalent direct validation. With a normal dict, Python iterates keys, so the current check primarily tests top-level field/map keys rather than nested values.","triggerScenarios":"Calling YourYamlModelWithoutDefault.from_yaml_file(path), YourYamlModelWithoutDefault.model_validate(data), or its constructor with input containing a top-level element whose text contains 'YOUR', such as a key named YOUR_MODEL_NAME_1. It is intended to trigger when config2.yaml or a copied example still has placeholders such as YOUR_API_KEY, YOUR_BASE_URL, or YOUR_PROXY; a nested api_key: YOUR_API_KEY is what users usually intend it to catch, though the implementation's top-level scan can miss it depending on the input shape. Pydantic normally wraps the raised ValueError in a ValidationError.","commonSituations":"Copying config/config2.example.yaml or running metagpt --init-config and never replacing the YOUR_* placeholders; editing the repository config/config2.yaml while MetaGPT actually loads ~/.metagpt/config2.yaml; CI or Docker runs where the intended config2.yaml secret was not mounted; copied multi-model examples that leave YOUR_MODEL_NAME_1/YOUR_MODEL_NAME_2 keys in place; retaining unused example tool sections filled with placeholders.","solutions":["Identify the file actually loaded using MetaGPT's priority order, especially ~/.metagpt/config2.yaml versus METAGPT_ROOT/config/config2.yaml, and open that exact file.","Replace every remaining YOUR_* placeholder with a real value; never commit the completed file or paste the secret into chat/logs.","For example/tool blocks you do not use, delete or comment out the entire block instead of leaving YOUR_* values or keys.","In multi-model maps, replace placeholder keys such as YOUR_MODEL_NAME_1 with real model names and provide their api_key/base_url entries.","In CI/Docker, verify the mounted config2.yaml exists at the expected path and contains no template text before starting MetaGPT."],"exampleFix":"# before (~/.metagpt/config2.yaml)\nllm:\n  api_type: openai\n  base_url: https://api.openai.com/v1\n  api_key: \"YOUR_API_KEY\"\n\n# after: put the real key in this local, untracked file\nllm:\n  api_type: openai\n  base_url: https://api.openai.com/v1\n  api_key: \"sk-REDACTED-REAL-KEY\"","handlingStrategy":"validation","validationCode":"from pathlib import Path\nimport yaml\n\ndef has_yaml_placeholder(node) -> bool:\n    if isinstance(node, str):\n        return \"YOUR\" in node\n    if isinstance(node, dict):\n        return any(\"YOUR\" in str(k) or has_yaml_placeholder(v) for k, v in node.items())\n    if isinstance(node, list):\n        return any(has_yaml_placeholder(item) for item in node)\n    return False\n\npath = Path.home() / \".metagpt/config2.yaml\"\nraw = yaml.safe_load(path.read_text(encoding=\"utf-8\"))\nif has_yaml_placeholder(raw):\n    raise RuntimeError(f\"Replace every YOUR_* placeholder in {path} before loading it\")\nmodel = YourYamlModelWithoutDefault.from_yaml_file(path)","typeGuard":"from typing import Any, TypeGuard\n\ndef is_placeholder_free_config(data: object) -> TypeGuard[dict[str, Any]]:\n    return isinstance(data, dict) and not has_yaml_placeholder(data)\n\nraw = yaml.safe_load(path.read_text(encoding=\"utf-8\"))\nif not is_placeholder_free_config(raw):\n    raise RuntimeError(\"config2.yaml still contains YOUR_* placeholders\")","tryCatchPattern":"from pydantic import ValidationError\n\ntry:\n    model = YourYamlModelWithoutDefault.model_validate(raw)\nexcept ValidationError as exc:\n    if \"Please set your config in config2.yaml\" in str(exc):\n        raise RuntimeError(\"Unconfigured MetaGPT config: replace all YOUR_* placeholders\") from exc\n    raise","preventionTips":["After copying an example config, search it for 'YOUR' before running MetaGPT: grep -n 'YOUR' ~/.metagpt/config2.yaml.","Keep config2.yaml local and untracked; load real secrets from your secret manager when generating it.","Delete unused example sections instead of leaving placeholder-filled blocks in the YAML.","In CI, add a startup check that fails the job when the mounted config2.yaml contains template text.","Remember the load priority: ~/.metagpt/config2.yaml overrides config/config2.yaml, so edit the file actually loaded."],"tags":["configuration","yaml","pydantic","placeholders","python"],"backgroundTag":null,"analyzedSha":"11cdf466d042aece04fc6cfd13b28e1a70341b1f","analyzedAt":"2026-08-14T23:20:02.994Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}