Spaces:
Sleeping
Sleeping
File size: 7,866 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 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | import logging
import os
from datetime import datetime
from typing import List, Optional
from dotenv import load_dotenv
from langchain_google_genai import ChatGoogleGenerativeAI
from langgraph.prebuilt import create_react_agent
from llm.connection_manager import get_checkpointer
from llm.prompt import system_message
from llm.tools import initialize_tools
from llm.utils import cleanup_old_tool_results, get_tool_result
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Global agent instance
_agent_executor = None
# Counter for periodic cleanup
_request_count = 0
def _get_agent():
"""Get or create the agent instance."""
global _agent_executor
if _agent_executor is None:
# Build LLM
print("[AGENT] building LLM")
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
# Build tools
print("[AGENT] initializing tools")
tools = initialize_tools()
# Create agent with fresh checkpointer
print("[AGENT] creating agent")
_agent_executor = create_react_agent(
llm,
tools=tools,
prompt=system_message,
checkpointer=get_checkpointer(),
)
return _agent_executor
def _build_message_with_context(message: str, selected_images: Optional[List[dict]], user_id: str) -> str:
"""Build the full message with image context if provided."""
if not selected_images or len(selected_images) == 0:
return message
image_context = "\n\nSelected Images:\n"
for i, img in enumerate(selected_images, 1):
image_context += f"{i}. {img.get('title', 'Untitled')} (ID: {img.get('id', 'unknown')})\n"
image_context += f" Type: {img.get('type', 'unknown')}\n"
image_context += f" Description: {img.get('description', 'No description')}\n"
if img.get("url"):
image_context += f" URL: {img.get('url')}\n"
image_context += "\n"
return message + image_context + f"\n\nUser ID: {user_id}"
def _extract_agent_response(response) -> str:
"""Extract the agent's response text from the response object."""
if not response or "messages" not in response or len(response["messages"]) == 0:
return "I'm sorry, I couldn't process your request. Please try again."
last_message = response["messages"][-1]
# Handle None or unexpected message types
if last_message is None:
return "I'm sorry, I couldn't process your request. Please try again."
# Handle both AIMessage objects and dictionaries
if hasattr(last_message, "content"):
content = last_message.content
if content is None:
return "I'm sorry, I couldn't process your request. Please try again."
return content
elif isinstance(last_message, dict) and "content" in last_message:
content = last_message["content"]
if content is None:
return "I'm sorry, I couldn't process your request. Please try again."
return content
return "I'm sorry, I couldn't process your request. Please try again."
def _generate_presigned_url(user_id: str, image_id: str) -> Optional[str]:
"""Generate a presigned URL for an image."""
import boto3
s3_client = boto3.client(
"s3",
region_name=os.environ.get("AWS_REGION", "us-east-1"),
aws_access_key_id=os.environ.get("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=os.environ.get("AWS_SECRET_ACCESS_KEY"),
)
bucket_name = os.environ.get("AWS_S3_BUCKET_NAME")
if not bucket_name:
print("[AGENT] AWS_S3_BUCKET_NAME not set")
return None
try:
s3_key = f"users/{user_id}/images/{image_id}"
print(f"[AGENT] Generating presigned URL for S3 key: {s3_key}")
presigned_url = s3_client.generate_presigned_url(
"get_object",
Params={"Bucket": bucket_name, "Key": s3_key},
ExpiresIn=7200, # 2 hours
)
print(f"[AGENT] Generated presigned URL: {presigned_url[:50]}...")
return presigned_url
except Exception as e:
print(f"[AGENT] Error generating presigned URL: {e}")
return None
def _process_generated_image(user_id: str, tool_result: dict) -> Optional[dict]:
"""Process a generated image tool result and return image data."""
image_id = tool_result.get("image_id")
title = tool_result.get("title", "Generated Image")
prompt = tool_result.get("prompt", "Based on your request")
if not image_id:
print("[AGENT] No image_id found in tool result")
return None
print(f"[AGENT] Processing generated image with ID: {image_id}")
# Generate presigned URL
presigned_url = _generate_presigned_url(user_id, image_id)
if not presigned_url:
return None
# Create image data structure using data from tool result
generated_image_data = {
"id": image_id,
"url": presigned_url,
"title": title,
"description": f"AI-generated image: {prompt}",
"timestamp": datetime.now().isoformat(),
"type": "generated",
}
print(f"[AGENT] Created generated_image_data: {generated_image_data}")
return generated_image_data
def _process_tool_results(user_id: str) -> Optional[dict]:
"""Process any tool results for the user and return generated image data if found."""
print(f"[AGENT] Checking for tool results for user {user_id}")
tool_result = get_tool_result(user_id, "generate_image")
if tool_result:
print(f"[AGENT] Found tool result: {tool_result}")
return _process_generated_image(user_id, tool_result)
else:
print(f"[AGENT] No tool result found for user {user_id}")
return None
def chat_with_agent(
message: str,
client_ip: str,
user_id: str = "default",
selected_images: Optional[List[dict]] = None,
) -> tuple[str, Optional[dict]]:
"""
Send a message to the agent and get a response.
Args:
message: The user's message
user_id: Unique identifier for the user/thread
selected_images: List of selected image objects (optional)
client_ip: IP address of the client
Returns:
Tuple of (agent_response, generated_image_data)
"""
global _request_count
# Periodic cleanup every 10 requests
_request_count += 1
if _request_count % 10 == 0:
print(f"[AGENT] Running periodic cleanup (request #{_request_count})")
cleanup_old_tool_results()
print(f"[AGENT] Starting chat_with_agent - user_id: {user_id}, message: {message[:100]}...")
agent = _get_agent()
# Prepare the message with context
print("[AGENT] building message with context")
full_message = _build_message_with_context(message, selected_images, user_id)
# Configure thread ID for conversation continuity
config = {"configurable": {"thread_id": user_id, "client_ip": client_ip}}
# Get response from agent
print(f"[AGENT] Invoking agent with config: {config}")
response = agent.invoke({"messages": [{"role": "user", "content": full_message}]}, config=config)
print(f"[AGENT] Agent response received: {type(response)}")
# Extract the agent's response
agent_response = _extract_agent_response(response)
print(f"[AGENT] Extracted agent response: {agent_response[:100]}...")
# Check for tool results and process generated images
generated_image_data = _process_tool_results(user_id)
print(f"[AGENT] Returning response - agent_response length: {len(agent_response)}, generated_image_data: {generated_image_data is not None}")
return agent_response, generated_image_data
if __name__ == "__main__":
# Test the agent
response = chat_with_agent("Hello! How can you help me with image editing?", "127.0.0.1")
print(response)
|