From b5fd238451d538b0044f4c1937c54d5f9767d6f3 Mon Sep 17 00:00:00 2001 From: caydyan Date: Sun, 14 Jun 2026 22:18:59 +0800 Subject: [PATCH] Add Anthropic prefill messages --- .env.example | 3 ++- README.md | 4 +++- helpers/devcontainer_helpers.py | 8 +++---- helpers/llm_message_helpers.py | 37 ++++++++++++++++++++++++++++ tests/test_llm_message_helpers.py | 40 +++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 helpers/llm_message_helpers.py create mode 100644 tests/test_llm_message_helpers.py diff --git a/.env.example b/.env.example index ab3da74..ba4f7fd 100644 --- a/.env.example +++ b/.env.example @@ -2,8 +2,9 @@ AZURE_OPENAI_API_KEY=YOURKEY AZURE_OPENAI_ENDPOINT=https://YOURENDPOINT.openai.azure.com AZURE_OPENAI_API_VERSION=2024-02-01 MODEL=gpt-4o-mini +ANTHROPIC_PREFILL_PROMPT={ EMBEDDING=text-embedding-3-small EMBEDDING_MODEL_MAX_TOKENS=8192 GITHUB_TOKEN=YOURTOKEN SUPABASE_URL=https://YOURLINK.supabase.co -SUPABASE_KEY=your_supabase_key \ No newline at end of file +SUPABASE_KEY=your_supabase_key diff --git a/README.md b/README.md index 4325025..855f3bf 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ To run this project in Daytona, you'll need to have Daytona installed. Follow th AZURE_OPENAI_API_KEY=your_azure_openai_api_key AZURE_OPENAI_API_VERSION=your_azure_openai_api_version MODEL=your_model_name + ANTHROPIC_PREFILL_PROMPT=optional_prefill_for_claude_models GITHUB_TOKEN=your_github_token ``` @@ -85,6 +86,7 @@ AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint AZURE_OPENAI_API_KEY=your_azure_openai_api_key AZURE_OPENAI_API_VERSION=your_azure_openai_api_version MODEL=your_model_name +ANTHROPIC_PREFILL_PROMPT=optional_prefill_for_claude_models GITHUB_TOKEN=your_github_token ``` @@ -128,4 +130,4 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file --- -Thank you for using **devcontainer-generator**! If you have any questions or issues, feel free to open an issue on GitHub. \ No newline at end of file +Thank you for using **devcontainer-generator**! If you have any questions or issues, feel free to open an issue on GitHub. diff --git a/helpers/devcontainer_helpers.py b/helpers/devcontainer_helpers.py index ef4f34c..efde878 100644 --- a/helpers/devcontainer_helpers.py +++ b/helpers/devcontainer_helpers.py @@ -6,6 +6,7 @@ import jsonschema import tiktoken from helpers.jinja_helper import process_template +from helpers.llm_message_helpers import build_devcontainer_messages from schemas import DevContainerModel from supabase_client import supabase from models import DevContainer @@ -92,10 +93,7 @@ def generate_devcontainer_json(instructor_client, repo_url, repo_context, devcon response = instructor_client.chat.completions.create( model=os.getenv("MODEL"), response_model=DevContainerModel, - messages=[ - {"role": "system", "content": "You are a helpful assistant that generates devcontainer.json files."}, - {"role": "user", "content": prompt}, - ], + messages=build_devcontainer_messages(prompt, os.getenv("MODEL")), ) devcontainer_json = json.dumps(response.dict(exclude_none=True), indent=2) @@ -136,4 +134,4 @@ def save_devcontainer(new_devcontainer): return result.data[0] if result.data else None except Exception as e: logging.error(f"Error saving devcontainer to Supabase: {str(e)}") - raise \ No newline at end of file + raise diff --git a/helpers/llm_message_helpers.py b/helpers/llm_message_helpers.py new file mode 100644 index 0000000..85978e9 --- /dev/null +++ b/helpers/llm_message_helpers.py @@ -0,0 +1,37 @@ +import os + + +ANTHROPIC_PREFILL_ENV_VAR = "ANTHROPIC_PREFILL_PROMPT" +DEFAULT_ANTHROPIC_PREFILL_PROMPT = "{" + + +def is_anthropic_model(model_name): + if not model_name: + return False + + normalized_model = model_name.strip().lower() + return normalized_model.startswith("claude") or ( + "anthropic" in normalized_model and "claude" in normalized_model + ) + + +def get_anthropic_prefill_prompt(): + configured_prefill = os.getenv(ANTHROPIC_PREFILL_ENV_VAR, DEFAULT_ANTHROPIC_PREFILL_PROMPT) + return configured_prefill.strip() or DEFAULT_ANTHROPIC_PREFILL_PROMPT + + +def build_devcontainer_messages(prompt, model_name=None): + messages = [ + {"role": "system", "content": "You are a helpful assistant that generates devcontainer.json files."}, + {"role": "user", "content": prompt}, + ] + + if is_anthropic_model(model_name): + messages.append( + { + "role": "assistant", + "content": get_anthropic_prefill_prompt(), + } + ) + + return messages diff --git a/tests/test_llm_message_helpers.py b/tests/test_llm_message_helpers.py new file mode 100644 index 0000000..2647def --- /dev/null +++ b/tests/test_llm_message_helpers.py @@ -0,0 +1,40 @@ +import os +import unittest +from unittest.mock import patch + +from helpers.llm_message_helpers import build_devcontainer_messages, is_anthropic_model + + +class LlmMessageHelpersTest(unittest.TestCase): + def test_non_anthropic_models_keep_original_messages(self): + messages = build_devcontainer_messages("render this prompt", "gpt-4o-mini") + + self.assertEqual( + messages, + [ + {"role": "system", "content": "You are a helpful assistant that generates devcontainer.json files."}, + {"role": "user", "content": "render this prompt"}, + ], + ) + + def test_anthropic_models_add_default_prefill(self): + messages = build_devcontainer_messages("render this prompt", "claude-3-5-sonnet-20241022") + + self.assertEqual(messages[-1], {"role": "assistant", "content": "{"}) + self.assertEqual(len(messages), 3) + + def test_anthropic_prefill_can_be_overridden(self): + with patch.dict(os.environ, {"ANTHROPIC_PREFILL_PROMPT": '{"name":'}, clear=False): + messages = build_devcontainer_messages("render this prompt", "anthropic/claude-3-haiku") + + self.assertEqual(messages[-1], {"role": "assistant", "content": '{"name":'}) + + def test_anthropic_model_detection_requires_claude_context(self): + self.assertTrue(is_anthropic_model("claude-3-opus")) + self.assertTrue(is_anthropic_model("anthropic/claude-3-haiku")) + self.assertFalse(is_anthropic_model("gpt-4o-mini")) + self.assertFalse(is_anthropic_model("some-anthropic-compatible-model")) + + +if __name__ == "__main__": + unittest.main()