This commit is contained in:
Slipstream 2025-04-29 16:54:55 -06:00
parent af43a3f64f
commit eecdcb5b7e
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 33 additions and 15 deletions

View File

@ -692,12 +692,12 @@ async def get_ai_response(cog: 'GurtCog', message: discord.Message, model_name:
continue continue
# Handle potential multimodal content in history (if stored that way) # Handle potential multimodal content in history (if stored that way)
if isinstance(msg.get("content"), list): if isinstance(msg.get("content"), list):
parts = [types.Part.from_text(part["text"]) if part["type"] == "text" else types.Part.from_uri(part["image_url"]["url"], mime_type=part["image_url"]["url"].split(";")[0].split(":")[1]) if part["type"] == "image_url" else None for part in msg["content"]] parts = [types.Part(text=part["text"]) if part["type"] == "text" else types.Part(uri=part["image_url"]["url"], mime_type=part["image_url"]["url"].split(";")[0].split(":")[1]) if part["type"] == "image_url" else None for part in msg["content"]]
parts = [p for p in parts if p] # Filter out None parts parts = [p for p in parts if p] # Filter out None parts
if parts: if parts:
contents.append(types.Content(role=role, parts=parts)) contents.append(types.Content(role=role, parts=parts))
elif isinstance(msg.get("content"), str): elif isinstance(msg.get("content"), str):
contents.append(types.Content(role=role, parts=[types.Part.from_text(msg["content"])])) contents.append(types.Content(role=role, parts=[types.Part(text=msg["content"])]))
# --- Prepare the current message content (potentially multimodal) --- # --- Prepare the current message content (potentially multimodal) ---
@ -723,7 +723,7 @@ async def get_ai_response(cog: 'GurtCog', message: discord.Message, model_name:
mentions_str = ", ".join([f"{m['display_name']}(id:{m['id']})" for m in formatted_current_message["mentioned_users_details"]]) mentions_str = ", ".join([f"{m['display_name']}(id:{m['id']})" for m in formatted_current_message["mentioned_users_details"]])
text_content += f"\n(Message Details: Mentions=[{mentions_str}])" text_content += f"\n(Message Details: Mentions=[{mentions_str}])"
current_message_parts.append(types.Part.from_text(text_content)) current_message_parts.append(types.Part(text=text_content))
# --- End text content construction --- # --- End text content construction ---
if message.attachments: if message.attachments:
@ -750,23 +750,23 @@ async def get_ai_response(cog: 'GurtCog', message: discord.Message, model_name:
try: try:
# 1. Add text part instructing AI about the file # 1. Add text part instructing AI about the file
instruction_text = f"User attached a file: '{filename}' (Type: {mime_type}). Analyze this file from the following URI and incorporate your understanding into your response." instruction_text = f"User attached a file: '{filename}' (Type: {mime_type}). Analyze this file from the following URI and incorporate your understanding into your response."
current_message_parts.append(types.Part.from_text(instruction_text)) current_message_parts.append(types.Part(text=instruction_text))
print(f"Added text instruction for attachment: {filename}") print(f"Added text instruction for attachment: {filename}")
# 2. Add the URI part # 2. Add the URI part
# Ensure mime_type doesn't contain parameters like '; charset=...' if the API doesn't like them # Ensure mime_type doesn't contain parameters like '; charset=...' if the API doesn't like them
clean_mime_type = mime_type.split(';')[0] clean_mime_type = mime_type.split(';')[0]
current_message_parts.append(types.Part.from_uri(uri=file_url, mime_type=clean_mime_type)) current_message_parts.append(types.Part(uri=file_url, mime_type=clean_mime_type))
print(f"Added URI part for attachment: {filename} ({clean_mime_type}) using URL: {file_url}") print(f"Added URI part for attachment: {filename} ({clean_mime_type}) using URL: {file_url}")
except Exception as e: except Exception as e:
print(f"Error creating types.Part for attachment {filename} ({mime_type}): {e}") print(f"Error creating types.Part for attachment {filename} ({mime_type}): {e}")
# Optionally add a text part indicating the error # Optionally add a text part indicating the error
current_message_parts.append(types.Part.from_text(f"(System Note: Failed to process attachment '{filename}' - {e})")) current_message_parts.append(types.Part(text=f"(System Note: Failed to process attachment '{filename}' - {e})"))
else: else:
print(f"Skipping unsupported or invalid attachment: {filename} (Type: {mime_type}, URL: {file_url})") print(f"Skipping unsupported or invalid attachment: {filename} (Type: {mime_type}, URL: {file_url})")
# Optionally inform the AI that an unsupported file was attached # Optionally inform the AI that an unsupported file was attached
current_message_parts.append(types.Part.from_text(f"(System Note: User attached an unsupported file '{filename}' of type '{mime_type}' which cannot be processed.)")) current_message_parts.append(types.Part(text=f"(System Note: User attached an unsupported file '{filename}' of type '{mime_type}' which cannot be processed.)"))
# Ensure there's always *some* content part, even if only text or errors # Ensure there's always *some* content part, even if only text or errors
@ -774,7 +774,7 @@ async def get_ai_response(cog: 'GurtCog', message: discord.Message, model_name:
contents.append(types.Content(role="user", parts=current_message_parts)) contents.append(types.Content(role="user", parts=current_message_parts))
else: else:
print("Warning: No content parts generated for user message.") print("Warning: No content parts generated for user message.")
contents.append(types.Content(role="user", parts=[types.Part.from_text("")])) # Ensure content list isn't empty contents.append(types.Content(role="user", parts=[types.Part(text="")])) # Ensure content list isn't empty
# --- Prepare Tools --- # --- Prepare Tools ---
# Convert FunctionDeclarations from config.py (TOOLS) into a Tool object # Convert FunctionDeclarations from config.py (TOOLS) into a Tool object
@ -1113,7 +1113,7 @@ async def get_proactive_ai_response(cog: 'GurtCog', message: discord.Message, tr
] ]
# Add the final instruction # Add the final instruction
proactive_contents.append( proactive_contents.append(
types.Content(role="user", parts=[types.Part.from_text( types.Content(role="user", parts=[types.Part(text=
f"Generate the response based on your plan. **CRITICAL: Your response MUST be ONLY the raw JSON object matching this schema:**\n\n{json.dumps(RESPONSE_SCHEMA['schema'], indent=2)}\n\n**Ensure nothing precedes or follows the JSON.**" f"Generate the response based on your plan. **CRITICAL: Your response MUST be ONLY the raw JSON object matching this schema:**\n\n{json.dumps(RESPONSE_SCHEMA['schema'], indent=2)}\n\n**Ensure nothing precedes or follows the JSON.**"
)]) )])
) )
@ -1252,25 +1252,25 @@ async def get_internal_ai_json_response(
if isinstance(content_value, str): if isinstance(content_value, str):
# Handle simple string content # Handle simple string content
message_parts.append(types.Part.from_text(content_value)) message_parts.append(types.Part(text=content_value))
elif isinstance(content_value, list): elif isinstance(content_value, list):
# Handle list content (e.g., multimodal from ProfileUpdater) # Handle list content (e.g., multimodal from ProfileUpdater)
for part_data in content_value: for part_data in content_value:
part_type = part_data.get("type") part_type = part_data.get("type")
if part_type == "text": if part_type == "text":
text = part_data.get("text", "") text = part_data.get("text", "")
message_parts.append(types.Part.from_text(text)) message_parts.append(types.Part(text=text))
elif part_type == "image_data": elif part_type == "image_data":
mime_type = part_data.get("mime_type") mime_type = part_data.get("mime_type")
base64_data = part_data.get("data") base64_data = part_data.get("data")
if mime_type and base64_data: if mime_type and base64_data:
try: try:
image_bytes = base64.b64decode(base64_data) image_bytes = base64.b64decode(base64_data)
message_parts.append(types.Part.from_data(data=image_bytes, mime_type=mime_type)) message_parts.append(types.Part(data=image_bytes, mime_type=mime_type))
except Exception as decode_err: except Exception as decode_err:
print(f"Error decoding/adding image part in get_internal_ai_json_response: {decode_err}") print(f"Error decoding/adding image part in get_internal_ai_json_response: {decode_err}")
# Optionally add a placeholder text part indicating failure # Optionally add a placeholder text part indicating failure
message_parts.append(types.Part.from_text("(System Note: Failed to process an image part)")) message_parts.append(types.Part(text="(System Note: Failed to process an image part)"))
else: else:
print("Warning: image_data part missing mime_type or data.") print("Warning: image_data part missing mime_type or data.")
else: else:
@ -1292,9 +1292,9 @@ async def get_internal_ai_json_response(
f"**Ensure nothing precedes or follows the JSON.**" f"**Ensure nothing precedes or follows the JSON.**"
) )
if contents and contents[-1].role == "user": if contents and contents[-1].role == "user":
contents[-1].parts.append(types.Part.from_text(f"\n\n{json_instruction_content}")) contents[-1].parts.append(types.Part(text=f"\n\n{json_instruction_content}"))
else: else:
contents.append(types.Content(role="user", parts=[types.Part.from_text(json_instruction_content)])) contents.append(types.Content(role="user", parts=[types.Part(text=json_instruction_content)]))
# --- Determine Model --- # --- Determine Model ---

18
test_part.py Normal file
View File

@ -0,0 +1,18 @@
# Test script for Part constructor
try:
from gurt.api import types
print("Successfully imported types module")
# Test creating a Part with text
part = types.Part(text="test")
print(f"Successfully created Part with text: {part}")
# Test creating a Part with URI
part_uri = types.Part(uri="https://example.com", mime_type="text/plain")
print(f"Successfully created Part with URI: {part_uri}")
print("All tests passed!")
except Exception as e:
print(f"Error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()