Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
SUPABASE_KEY=your_supabase_key
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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.
Thank you for using **devcontainer-generator**! If you have any questions or issues, feel free to open an issue on GitHub.
8 changes: 3 additions & 5 deletions helpers/devcontainer_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
raise
37 changes: 37 additions & 0 deletions helpers/llm_message_helpers.py
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions tests/test_llm_message_helpers.py
Original file line number Diff line number Diff line change
@@ -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()