|
|
""" |
|
|
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): |
|
|
""" |
|
|
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 |
|
|
|
|
|
Returns: |
|
|
list[ImageAdEssentials]: List of psychology triggers, angles, and concepts |
|
|
""" |
|
|
|
|
|
claude_client = Anthropic(api_key=ANTHROPIC_API_KEY) |
|
|
|
|
|
|
|
|
system_prompt = get_system_prompt() |
|
|
user_prompt = get_user_prompt(target_audience, product_category, product_description) |
|
|
|
|
|
|
|
|
json_schema = ImageAdEssentialsOutput.model_json_schema() |
|
|
json_schema = _add_additional_properties_false(json_schema) |
|
|
|
|
|
|
|
|
message = claude_client.messages.create( |
|
|
model="claude-opus-4-6", |
|
|
max_tokens=1024, |
|
|
system=system_prompt, |
|
|
messages=[ |
|
|
{ |
|
|
"role": "user", |
|
|
"content": user_prompt |
|
|
} |
|
|
], |
|
|
output_config={ |
|
|
"format": { |
|
|
"type": "json_schema", |
|
|
"schema": json_schema |
|
|
} |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
if message.stop_reason == "refusal": |
|
|
raise ValueError("Claude refused the request.") |
|
|
|
|
|
|
|
|
if message.stop_reason == "max_tokens": |
|
|
raise ValueError("Claude response was truncated — increase max_tokens.") |
|
|
|
|
|
|
|
|
response_data = json.loads(message.content[0].text) |
|
|
output = ImageAdEssentialsOutput(**response_data) |
|
|
return output.output |
|
|
|