File size: 5,865 Bytes
aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae f404908 aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae aa03f63 9064aae |
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 |
# FILE: config.py
# (Corrected and complete version with all constants and RateLimiter class)
import os
import dspy
import logging
import time
from datetime import datetime, timedelta
from dotenv import load_dotenv
from dspy_llm_wrapper import SyncCustomGeminiDspyLM
# Load environment variables from a .env file
load_dotenv()
# Setup basic logging
logging.basicConfig(level=logging.INFO, format='{levelname} {asctime} {name}: {message}', style='{')
logger = logging.getLogger(__name__)
# --- Rate Limiter Class ---
# Integrated as per your request. App.py will instantiate this.
class RateLimiter:
"""Manages call frequency to an API to stay within defined limits."""
def __init__(self, max_calls: int = 7, time_period: int = 60):
"""
Initializes the RateLimiter.
Args:
max_calls (int): The maximum number of calls allowed in the time period.
time_period (int): The time period in seconds.
"""
if not isinstance(max_calls, int) or max_calls <= 0:
raise ValueError("max_calls must be a positive integer.")
if not isinstance(time_period, int) or time_period <= 0:
raise ValueError("time_period must be a positive integer.")
self.max_calls = max_calls
self.time_period = time_period
self.call_timestamps = []
self.total_calls_made = 0
logger.info(f"Rate Limiter Initialized: Max {self.max_calls} calls per {self.time_period} seconds.")
def wait_if_needed(self):
"""
Blocks execution if the call rate would be exceeded. Waits until the next call is allowed.
"""
# Prune timestamps older than the time period
current_time = datetime.now()
self.call_timestamps = [ts for ts in self.call_timestamps if current_time - ts < timedelta(seconds=self.time_period)]
# If the limit is reached, calculate wait time
if len(self.call_timestamps) >= self.max_calls:
oldest_call_in_window = self.call_timestamps[0]
time_to_wait = (oldest_call_in_window + timedelta(seconds=self.time_period)) - current_time
wait_seconds = time_to_wait.total_seconds()
if wait_seconds > 0:
logger.warning(f"[Rate Limiter]: Limit of {self.max_calls} calls reached. Waiting for {wait_seconds:.2f} seconds.")
time.sleep(wait_seconds) # Block execution
# Record the new call timestamp and proceed
self.call_timestamps.append(datetime.now())
self.total_calls_made += 1
logger.info(f"[Rate Limiter]: Call #{self.total_calls_made} permitted.")
# --- DSPy Initialization ---
API_KEY = os.getenv("GOOGLE_API_KEY")
def initialize_dspy(
model_name: str = "gemini/gemini-2.5-flash-preview-04-17",
api_key: str = None,
**kwargs
) -> "SyncCustomGeminiDspyLM | None":
logging.info(f"Attempting to initialize DSPy with model: {model_name}")
# 1. Resolve API Key from argument or environment variable
resolved_api_key = api_key or os.getenv("GOOGLE_API_KEY")
if not resolved_api_key:
logging.error(
"FATAL: GOOGLE_API_KEY not provided as an argument or found in environment variables."
)
return None
# 2. Instantiate the Language Model with default kwargs
lm_kwargs = {"temperature": 0.7, **kwargs}
try:
custom_lm = SyncCustomGeminiDspyLM(
model=model_name,
api_key=resolved_api_key,
**lm_kwargs
)
except Exception as e:
logging.error(f"Failed to instantiate SyncCustomGeminiDspyLM: {e}", exc_info=True)
return None
# 3. Perform a live verification call to ensure credentials are valid
try:
logging.info("Verifying credentials with a test API call...")
# Use the LM's __call__ directly for a simple, raw test
test_response = custom_lm(prompt="Hello, this is a test. Respond with 'OK'.")
# The __call__ method returns a list. Check for an empty list or an error message.
if not test_response or "[ERROR:" in test_response[0]:
raise ValueError(f"Test call failed. Response: {test_response}")
logging.info("LM verification successful.")
except Exception as e:
logging.error(
f"FATAL: LM verification failed for model '{model_name}'. "
f"Please check your API key, model name, and network connection. Error: {e}",
exc_info=True
)
return None
# 4. Configure DSPy glo bally if verification passes
dspy.settings.configure(lm=custom_lm)
logging.info(
f"DSPy successfully configured globally with validated model: {model_name}"
)
return test_response# --- Application Constants ---
# State Keys
STATE_STAGE = "stage"
STATE_HISTORY = "conversation_history"
STATE_FINAL_SYLLABUS = "final_syllabus_xml"
STATE_EXPLAINER_PROMPT = "explainer_system_prompt"
STATE_EXPLANATION_START_INDEX = "explanation_start_index"
STATE_CURRENT_TITLE = "current_title"
STATE_GENERATED_TITLE = "generated_title"
STATE_RESOURCE_SUMMARY_OVERVIEW = "resource_summary_overview_for_manager"
STATE_RESOURCE_TYPE_FOR_SYLLABUS = "resource_type_for_syllabus_gen"
STATE_RESOURCE_CONTENT_JSON_FOR_SYLLABUS = "resource_content_json_for_syllabus_gen"
STATE_UPLOADED_FILENAMES = "uploaded_filenames_list"
STATE_IS_FIRST_TURN = "is_first_turn" # New state key for file upload logic
# Flag Keys
STATE_DISPLAY_SYLLABUS_FLAG = "display_syllabus_flag"
STATE_TRANSITION_EXPLAINER_FLAG = "transition_to_explainer_flag"
# Stage Names
STAGE_START = "START"
STAGE_NEGOTIATING = "NEGOTIATING"
STAGE_EXPLAINING = "EXPLAINING"
STAGE_ERROR = "ERROR"
# Chat Logic Constants
DEFAULT_CHAT_TITLE = "New Chat"
TITLE_GENERATION_THRESHOLD = 4
TITLE_MAX_HISTORY_SNIPPET_FOR_TITLE = 6 |