File size: 3,235 Bytes
de08998
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76c3397
 
 
 
 
 
de08998
 
 
 
 
 
 
76c3397
de08998
 
 
 
76c3397
 
 
de08998
 
 
 
76c3397
 
 
de08998
 
 
 
 
 
 
 
76c3397
de08998
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
Claude-based researcher implementation.
Uses output_config.format for native structured outputs.
Ref: https://platform.claude.com/docs/en/build-with-claude/structured-outputs
"""
import json
from anthropic import Anthropic
from backend.pydantic_schema import ImageAdEssentialsOutput
from backend.prompt import get_system_prompt, get_user_prompt
from dotenv import load_dotenv
import os

load_dotenv()

ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")


def _add_additional_properties_false(schema: dict) -> dict:
    """
    Recursively add 'additionalProperties': false to all object types.
    Required by Claude's structured outputs.
    """
    if isinstance(schema, dict):
        if schema.get("type") == "object":
            schema["additionalProperties"] = False
        for value in schema.values():
            if isinstance(value, dict):
                _add_additional_properties_false(value)
            elif isinstance(value, list):
                for item in value:
                    if isinstance(item, dict):
                        _add_additional_properties_false(item)
    return schema


def researcher_claude(
    target_audience: str,
    product_category: str,
    product_description: str,
    count: int = 5,
):
    """
    Claude-based researcher function using native structured outputs.

    Args:
        target_audience: Target audience from the predefined list
        product_category: Product category (e.g., "ring", "bangles")
        product_description: Description of the product
        count: Number of psychology triggers (concepts/angles) to generate

    Returns:
        list[ImageAdEssentials]: List of psychology triggers, angles, and concepts
    """
    if not ANTHROPIC_API_KEY:
        raise ValueError("ANTHROPIC_API_KEY is not set in the environment.")

    claude_client = Anthropic(api_key=ANTHROPIC_API_KEY)

    # Get prompts
    system_prompt = get_system_prompt()
    user_prompt = get_user_prompt(
        target_audience, product_category, product_description, count
    )

    # Build JSON schema from Pydantic model and add required additionalProperties: false
    json_schema = ImageAdEssentialsOutput.model_json_schema()
    json_schema = _add_additional_properties_false(json_schema)

    # Use Claude's native structured outputs via output_config.format
    message = claude_client.messages.create(
        model="claude-opus-4-6",
        max_tokens=4096,
        system=system_prompt,
        messages=[
            {
                "role": "user",
                "content": user_prompt
            }
        ],
        output_config={
            "format": {
                "type": "json_schema",
                "schema": json_schema
            }
        }
    )

    # Check for safety refusal
    if message.stop_reason == "refusal":
        raise ValueError("Claude refused the request.")

    # Check if response was truncated
    if message.stop_reason == "max_tokens":
        raise ValueError("Claude response was truncated — increase max_tokens.")

    # Parse the JSON response and validate with Pydantic
    response_data = json.loads(message.content[0].text)
    output = ImageAdEssentialsOutput(**response_data)
    return output.output