File size: 7,221 Bytes
235461a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import uuid
from typing import Dict, Optional

import replicate
from dotenv import load_dotenv
from langchain_core.runnables import RunnableConfig, RunnableLambda
from pydantic import BaseModel

from llm.prompt import generate_image_tool_description
from llm.utils import create_or_update_ip_generation_count, get_ip_generation_count, store_tool_result, upload_generated_image_to_s3

load_dotenv()


# The generate_image tool's input schema
class GenerateImageToolInput(BaseModel):
    prompt: str
    user_id: str
    image_url: str
    title: Optional[str] = "Generated Image"


# The core function that generates an image of the tool
def _generate_image_core(
    prompt: str,
    user_id: str,
    image_url: str,
    title: str,
    client_ip: str,
) -> str:
    """
    Generate an image based on a prompt.
    """
    print(f"[TOOL] generate_image called with prompt: {prompt[:50]}..., user_id: {user_id}, image_url: {image_url[:50]}...")

    # Check if the user has exceeded the generation limit
    if get_ip_generation_count(client_ip) >= 10:
        print("[TOOL] User exceeded the generation limit of 10 this week.")
        return "Failed as user exceeded the max generation limit of 10 this week."

    use_sdxl = False  # True for testing purposes
    if use_sdxl:
        input = {
            "width": 768,
            "height": 768,
            "prompt": prompt,
            "refine": "expert_ensemble_refiner",
            "apply_watermark": False,
            "num_inference_steps": 25,
            "prompt_strength": 0.5,
            "image": image_url,
            "input_image": image_url,
            "output_format": "png",
        }
        version = "stability-ai/sdxl:" "7762fd07cf82c948538e41f63f77d685e02b063e37e496e96eefd46c929f9bdc"
        output = replicate.run(
            version,
            input=input,
        )
        generated_image_url = output[0] if isinstance(output, list) else output
    else:  # Flux Kontext Pro
        # Generate image using Replicate
        input = {
            "prompt": prompt,
            "input_image": image_url,
            "output_format": "png",
        }
        output = replicate.run(
            "black-forest-labs/flux-kontext-pro",
            input=input,
        )
        print(f"[TOOL] Replicate output: {output}")
        print(f"[TOOL] Output type: {type(output)}")
        print(f"[TOOL] Output length: {len(output) if hasattr(output, '__len__') else 'N/A'}")

        # Check if generation was successful
        if not output or (hasattr(output, "__len__") and len(output) == 0):
            print("[TOOL] Replicate generation failed - no output")
            return "Failed to generate image. Please try again."

        # Flux Kontext Pro returns a string URL
        generated_image_url = str(output)
        print(f"[TOOL] Generated image URL: {generated_image_url}")

    # Handle Flux Kontext Pro output format
    image_data: Optional[bytes] = None

    try:
        # Download the image from the URL
        import requests

        response = requests.get(generated_image_url)
        response.raise_for_status()
        image_data = response.content
        print(f"[TOOL] Downloaded image data, size: {len(image_data)} bytes")

    except Exception as e:
        print(f"[TOOL] Error processing output: {e}")
        return f"Failed to process generated image: {str(e)}"

    # Check if we successfully got image data
    if image_data is None:
        return "Failed to get image data from generation output"

    # Update or create a new generation count by + 1 for this ip address
    create_or_update_ip_generation_count(client_ip)

    # Generate unique ID for the image
    image_id = str(uuid.uuid4())

    # Upload to S3
    print(f"[TOOL] Uploading to S3 with image_id: {image_id}")
    print(f"[TOOL] Image data size: {len(image_data)} bytes")
    try:
        s3_result = upload_generated_image_to_s3(
            image_data=image_data,
            image_id=image_id,
            user_id=user_id,
            prompt=prompt,
            title=title,
        )
        print(f"[TOOL] S3 upload result: {s3_result}")
        print(f"[TOOL] S3 upload success: {s3_result.get('success', False)}")

        if s3_result["success"]:
            # Store structured result for the agent to retrieve
            tool_result = {"image_id": image_id, "title": title, "prompt": prompt, "success": True}
            print(f"[TOOL] About to store tool result: {tool_result}")
            store_tool_result(user_id, "generate_image", tool_result)
            print("[TOOL] Tool result stored successfully")

            result_msg = f"Image generated successfully! User can find it his/her gallery. \
                Image ID: {image_id}, Title: {title}"
            print(f"[TOOL] Returning success: {result_msg}")
            return result_msg
        else:
            error_msg = f"Image generated but failed to save: {s3_result.get('error', 'Unknown error')}"
            print(f"[TOOL] Returning error: {error_msg}")
            return error_msg

    except Exception as e:
        error_msg = f"Image generated but failed to save to storage: {str(e)}"
        print(f"[TOOL] Exception during S3 upload: {error_msg}")
        return error_msg

    finally:
        if image_data:
            # Clear image data from memory
            del image_data


def _generate_image_callable(inputs: Dict[str, str], config: RunnableConfig):
    # Normalize inputs whether dict or Pydantic
    if hasattr(inputs, "model_dump"):
        inputs = inputs.model_dump()
    elif hasattr(inputs, "dict"):
        inputs = inputs.dict()

    # Pull the IP from the per-invoke config
    cfg = config.get("configurable") or {}

    ip = cfg.get("client_ip")
    if not isinstance(ip, str) or not ip:
        # Fail fast if it's absent or not a string
        raise ValueError("client_ip is required in config.configurable and must be a non-empty string")

    client_ip: str = ip

    # Call your core with the IP
    return _generate_image_core(
        prompt=inputs["prompt"],
        user_id=inputs["user_id"],
        image_url=inputs["image_url"],
        title=inputs.get("title", "Generated Image"),
        client_ip=client_ip,
    )


def initialize_tools():
    """Initialize the tools for the agent."""
    print("[TOOLS] building generate_image tool")

    # A Runnable that receives (inputs, config) every invoke
    generate_image_runnable = RunnableLambda(_generate_image_callable)

    # As agent creation API expects "tools", convert the runnable to a Tool:
    generate_image_tool = generate_image_runnable.as_tool(
        name="generate_image",
        description=generate_image_tool_description,
        args_schema=GenerateImageToolInput,
    )

    return [generate_image_tool]


if __name__ == "__main__":
    # Test the tool
    generate_image = initialize_tools()[0]
    output = generate_image.invoke(
        {
            "prompt": "A woman in a beautiful sunset over a calm ocean",
            "user_id": "123",
            "image_url": "https://example.com/image.jpg",
            "title": "Test Image",
        },
        config={"configurable": {"client_ip": "127.0.0.1"}},
    )
    print(output)