Update config.py
Browse files
config.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
# FILE: config.py
|
2 |
-
# (Corrected and complete version with all constants)
|
3 |
|
4 |
import os
|
5 |
import dspy
|
6 |
import logging
|
|
|
|
|
7 |
from dotenv import load_dotenv
|
8 |
from dspy_llm_wrapper import SyncCustomGeminiDspyLM
|
9 |
|
@@ -14,43 +16,125 @@ load_dotenv()
|
|
14 |
logging.basicConfig(level=logging.INFO, format='{levelname} {asctime} {name}: {message}', style='{')
|
15 |
logger = logging.getLogger(__name__)
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
API_KEY = os.getenv("GOOGLE_API_KEY")
|
18 |
|
19 |
-
def initialize_dspy(
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
return None
|
24 |
-
|
|
|
|
|
25 |
try:
|
26 |
-
LITELLM_MODEL_STRING = "gemini/gemini-2.5-flash-preview-04-17"
|
27 |
custom_lm = SyncCustomGeminiDspyLM(
|
28 |
-
model=
|
29 |
-
api_key=
|
30 |
-
|
31 |
)
|
32 |
-
dspy.settings.configure(lm=custom_lm)
|
33 |
-
logger.info(f"DSPy configured globally with model: {LITELLM_MODEL_STRING}")
|
34 |
-
return custom_lm
|
35 |
except Exception as e:
|
36 |
-
|
37 |
return None
|
38 |
|
39 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
|
41 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
STATE_STAGE = "stage"
|
43 |
STATE_HISTORY = "conversation_history"
|
44 |
STATE_FINAL_SYLLABUS = "final_syllabus_xml"
|
45 |
STATE_EXPLAINER_PROMPT = "explainer_system_prompt"
|
46 |
STATE_EXPLANATION_START_INDEX = "explanation_start_index"
|
47 |
-
STATE_CURRENT_TITLE = "current_title"
|
48 |
-
STATE_GENERATED_TITLE = "generated_title"
|
49 |
STATE_RESOURCE_SUMMARY_OVERVIEW = "resource_summary_overview_for_manager"
|
50 |
STATE_RESOURCE_TYPE_FOR_SYLLABUS = "resource_type_for_syllabus_gen"
|
51 |
STATE_RESOURCE_CONTENT_JSON_FOR_SYLLABUS = "resource_content_json_for_syllabus_gen"
|
|
|
|
|
52 |
|
53 |
-
# Flag Keys
|
54 |
STATE_DISPLAY_SYLLABUS_FLAG = "display_syllabus_flag"
|
55 |
STATE_TRANSITION_EXPLAINER_FLAG = "transition_to_explainer_flag"
|
56 |
|
@@ -59,9 +143,8 @@ STAGE_START = "START"
|
|
59 |
STAGE_NEGOTIATING = "NEGOTIATING"
|
60 |
STAGE_EXPLAINING = "EXPLAINING"
|
61 |
STAGE_ERROR = "ERROR"
|
62 |
-
STATE_UPLOADED_FILENAMES = "uploaded_filenames_list"
|
63 |
|
64 |
# Chat Logic Constants
|
65 |
-
DEFAULT_CHAT_TITLE = "New Chat"
|
66 |
-
TITLE_GENERATION_THRESHOLD = 4
|
67 |
-
TITLE_MAX_HISTORY_SNIPPET_FOR_TITLE = 6
|
|
|
1 |
# FILE: config.py
|
2 |
+
# (Corrected and complete version with all constants and RateLimiter class)
|
3 |
|
4 |
import os
|
5 |
import dspy
|
6 |
import logging
|
7 |
+
import time
|
8 |
+
from datetime import datetime, timedelta
|
9 |
from dotenv import load_dotenv
|
10 |
from dspy_llm_wrapper import SyncCustomGeminiDspyLM
|
11 |
|
|
|
16 |
logging.basicConfig(level=logging.INFO, format='{levelname} {asctime} {name}: {message}', style='{')
|
17 |
logger = logging.getLogger(__name__)
|
18 |
|
19 |
+
# --- Rate Limiter Class ---
|
20 |
+
# Integrated as per your request. App.py will instantiate this.
|
21 |
+
class RateLimiter:
|
22 |
+
"""Manages call frequency to an API to stay within defined limits."""
|
23 |
+
def __init__(self, max_calls: int = 7, time_period: int = 60):
|
24 |
+
"""
|
25 |
+
Initializes the RateLimiter.
|
26 |
+
Args:
|
27 |
+
max_calls (int): The maximum number of calls allowed in the time period.
|
28 |
+
time_period (int): The time period in seconds.
|
29 |
+
"""
|
30 |
+
if not isinstance(max_calls, int) or max_calls <= 0:
|
31 |
+
raise ValueError("max_calls must be a positive integer.")
|
32 |
+
if not isinstance(time_period, int) or time_period <= 0:
|
33 |
+
raise ValueError("time_period must be a positive integer.")
|
34 |
+
|
35 |
+
self.max_calls = max_calls
|
36 |
+
self.time_period = time_period
|
37 |
+
self.call_timestamps = []
|
38 |
+
self.total_calls_made = 0
|
39 |
+
logger.info(f"Rate Limiter Initialized: Max {self.max_calls} calls per {self.time_period} seconds.")
|
40 |
+
|
41 |
+
def wait_if_needed(self):
|
42 |
+
"""
|
43 |
+
Blocks execution if the call rate would be exceeded. Waits until the next call is allowed.
|
44 |
+
"""
|
45 |
+
# Prune timestamps older than the time period
|
46 |
+
current_time = datetime.now()
|
47 |
+
self.call_timestamps = [ts for ts in self.call_timestamps if current_time - ts < timedelta(seconds=self.time_period)]
|
48 |
+
|
49 |
+
# If the limit is reached, calculate wait time
|
50 |
+
if len(self.call_timestamps) >= self.max_calls:
|
51 |
+
oldest_call_in_window = self.call_timestamps[0]
|
52 |
+
time_to_wait = (oldest_call_in_window + timedelta(seconds=self.time_period)) - current_time
|
53 |
+
wait_seconds = time_to_wait.total_seconds()
|
54 |
+
|
55 |
+
if wait_seconds > 0:
|
56 |
+
logger.warning(f"[Rate Limiter]: Limit of {self.max_calls} calls reached. Waiting for {wait_seconds:.2f} seconds.")
|
57 |
+
time.sleep(wait_seconds) # Block execution
|
58 |
+
|
59 |
+
# Record the new call timestamp and proceed
|
60 |
+
self.call_timestamps.append(datetime.now())
|
61 |
+
self.total_calls_made += 1
|
62 |
+
logger.info(f"[Rate Limiter]: Call #{self.total_calls_made} permitted.")
|
63 |
+
|
64 |
+
# --- DSPy Initialization ---
|
65 |
API_KEY = os.getenv("GOOGLE_API_KEY")
|
66 |
|
67 |
+
def initialize_dspy(
|
68 |
+
model_name: str = "gemini/gemini-2.5-flash-preview-04-17",
|
69 |
+
api_key: str = None,
|
70 |
+
**kwargs
|
71 |
+
) -> "SyncCustomGeminiDspyLM | None":
|
72 |
+
|
73 |
+
logging.info(f"Attempting to initialize DSPy with model: {model_name}")
|
74 |
+
|
75 |
+
# 1. Resolve API Key from argument or environment variable
|
76 |
+
resolved_api_key = api_key or os.getenv("GOOGLE_API_KEY")
|
77 |
+
if not resolved_api_key:
|
78 |
+
logging.error(
|
79 |
+
"FATAL: GOOGLE_API_KEY not provided as an argument or found in environment variables."
|
80 |
+
)
|
81 |
return None
|
82 |
+
|
83 |
+
# 2. Instantiate the Language Model with default kwargs
|
84 |
+
lm_kwargs = {"temperature": 0.7, **kwargs}
|
85 |
try:
|
|
|
86 |
custom_lm = SyncCustomGeminiDspyLM(
|
87 |
+
model=model_name,
|
88 |
+
api_key=resolved_api_key,
|
89 |
+
**lm_kwargs
|
90 |
)
|
|
|
|
|
|
|
91 |
except Exception as e:
|
92 |
+
logging.error(f"Failed to instantiate SyncCustomGeminiDspyLM: {e}", exc_info=True)
|
93 |
return None
|
94 |
|
95 |
+
# 3. Perform a live verification call to ensure credentials are valid
|
96 |
+
try:
|
97 |
+
logging.info("Verifying credentials with a test API call...")
|
98 |
+
# Use the LM's __call__ directly for a simple, raw test
|
99 |
+
test_response = custom_lm(prompt="Hello, this is a test. Respond with 'OK'.")
|
100 |
+
|
101 |
+
# The __call__ method returns a list. Check for an empty list or an error message.
|
102 |
+
if not test_response or "[ERROR:" in test_response[0]:
|
103 |
+
raise ValueError(f"Test call failed. Response: {test_response}")
|
104 |
+
|
105 |
+
|
106 |
+
logging.info("LM verification successful.")
|
107 |
+
except Exception as e:
|
108 |
+
logging.error(
|
109 |
+
f"FATAL: LM verification failed for model '{model_name}'. "
|
110 |
+
f"Please check your API key, model name, and network connection. Error: {e}",
|
111 |
+
exc_info=True
|
112 |
+
)
|
113 |
+
return None
|
114 |
|
115 |
+
# 4. Configure DSPy glo bally if verification passes
|
116 |
+
dspy.settings.configure(lm=custom_lm)
|
117 |
+
logging.info(
|
118 |
+
f"DSPy successfully configured globally with validated model: {model_name}"
|
119 |
+
)
|
120 |
+
|
121 |
+
return test_response# --- Application Constants ---
|
122 |
+
|
123 |
+
# State Keys
|
124 |
STATE_STAGE = "stage"
|
125 |
STATE_HISTORY = "conversation_history"
|
126 |
STATE_FINAL_SYLLABUS = "final_syllabus_xml"
|
127 |
STATE_EXPLAINER_PROMPT = "explainer_system_prompt"
|
128 |
STATE_EXPLANATION_START_INDEX = "explanation_start_index"
|
129 |
+
STATE_CURRENT_TITLE = "current_title"
|
130 |
+
STATE_GENERATED_TITLE = "generated_title"
|
131 |
STATE_RESOURCE_SUMMARY_OVERVIEW = "resource_summary_overview_for_manager"
|
132 |
STATE_RESOURCE_TYPE_FOR_SYLLABUS = "resource_type_for_syllabus_gen"
|
133 |
STATE_RESOURCE_CONTENT_JSON_FOR_SYLLABUS = "resource_content_json_for_syllabus_gen"
|
134 |
+
STATE_UPLOADED_FILENAMES = "uploaded_filenames_list"
|
135 |
+
STATE_IS_FIRST_TURN = "is_first_turn" # New state key for file upload logic
|
136 |
|
137 |
+
# Flag Keys
|
138 |
STATE_DISPLAY_SYLLABUS_FLAG = "display_syllabus_flag"
|
139 |
STATE_TRANSITION_EXPLAINER_FLAG = "transition_to_explainer_flag"
|
140 |
|
|
|
143 |
STAGE_NEGOTIATING = "NEGOTIATING"
|
144 |
STAGE_EXPLAINING = "EXPLAINING"
|
145 |
STAGE_ERROR = "ERROR"
|
|
|
146 |
|
147 |
# Chat Logic Constants
|
148 |
+
DEFAULT_CHAT_TITLE = "New Chat"
|
149 |
+
TITLE_GENERATION_THRESHOLD = 4
|
150 |
+
TITLE_MAX_HISTORY_SNIPPET_FOR_TITLE = 6
|