Dama12 commited on
Commit
7aeb053
·
1 Parent(s): a7a2c03

Fix K2 Think JSON parsing and secure default configs

Browse files
.env.example CHANGED
@@ -1,5 +1,5 @@
1
  # Database
2
- DATABASE_URL=postgresql://user:onion123@localhost:5432/scoinvestigator
3
  DB_ECHO=false
4
 
5
  # API Settings
@@ -8,12 +8,12 @@ API_VERSION=1.0.0
8
  API_DESCRIPTION=Advanced AI system for multi-document scientific analysis and experimental protocol design
9
 
10
  # Security (CHANGE IN PRODUCTION!)
11
- SECRET_KEY=your-super-secret-key-change-this-in-production-must-be-at-least-32-characters
12
  ALGORITHM=HS256
13
  ACCESS_TOKEN_EXPIRE_MINUTES=30
14
 
15
  # OpenAI API
16
- OPENAI_API_KEY=sk-your-openai-key-here
17
  LLM_MODEL=gpt-4-turbo
18
  EMBEDDINGS_MODEL=text-embedding-3-small
19
 
 
1
  # Database
2
+ DATABASE_URL=postgresql://user:CHANGE_THIS_PASSWORD@localhost:5432/scoinvestigator
3
  DB_ECHO=false
4
 
5
  # API Settings
 
8
  API_DESCRIPTION=Advanced AI system for multi-document scientific analysis and experimental protocol design
9
 
10
  # Security (CHANGE IN PRODUCTION!)
11
+ SECRET_KEY=CHANGE_THIS_TO_A_SECURE_RANDOM_STRING_32_CHARS_MINIMUM
12
  ALGORITHM=HS256
13
  ACCESS_TOKEN_EXPIRE_MINUTES=30
14
 
15
  # OpenAI API
16
+ OPENAI_API_KEY=your-openai-key-here
17
  LLM_MODEL=gpt-4-turbo
18
  EMBEDDINGS_MODEL=text-embedding-3-small
19
 
app/core/settings.py CHANGED
@@ -19,7 +19,7 @@ class Settings(BaseSettings):
19
  DEBUG: bool = True
20
 
21
  # Database — REQUIRED: must be set in .env
22
- DATABASE_URL: str = "postgresql://user:onion123@localhost:5432/scoinvestigator"
23
 
24
  @field_validator("DATABASE_URL", mode="before")
25
  @classmethod
@@ -31,7 +31,7 @@ class Settings(BaseSettings):
31
  DB_ECHO: bool = False
32
 
33
  # Security — REQUIRED: must be set in .env
34
- SECRET_KEY: str = "your-super-secret-key-change-this-in-production-must-be-at-least-32-characters"
35
  ALGORITHM: str = "HS256"
36
  ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
37
 
 
19
  DEBUG: bool = True
20
 
21
  # Database — REQUIRED: must be set in .env
22
+ DATABASE_URL: str = "postgresql://user:CHANGE_THIS_PASSWORD@localhost:5432/scoinvestigator"
23
 
24
  @field_validator("DATABASE_URL", mode="before")
25
  @classmethod
 
31
  DB_ECHO: bool = False
32
 
33
  # Security — REQUIRED: must be set in .env
34
+ SECRET_KEY: str = "CHANGE_THIS_TO_A_SECURE_RANDOM_STRING_32_CHARS_MINIMUM"
35
  ALGORITHM: str = "HS256"
36
  ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
37
 
app/services/k2_think_engine.py CHANGED
@@ -87,7 +87,7 @@ class K2ThinkEngine:
87
 
88
  parser = JsonOutputParser()
89
 
90
- # 4. Prompt autoritaire pour éviter la paresse (Laziness)
91
  instruction_prompt = f"""[SCIENTIFIC MISSION]
92
  Perform an EXHAUSTIVE and DETAILED comparative analysis of the attached documents.
93
  CRITICAL RULES:
@@ -97,33 +97,105 @@ CRITICAL RULES:
97
  - Your output must be a single, complete, and valid JSON object exactly matching the schema below.
98
  - You MUST populate ALL fields with complete, real text. Do NOT abbreviate the JSON output.
99
  - NEVER write `... JSON ...` or `{{"...": "..."}}`. You must write out the full, complete JSON object.
 
 
100
 
101
  [DOCUMENTS TO ANALYZE]
102
  {context}
103
 
104
- [REQUIRED SCHEMA]
105
  You must return a JSON object with the following structure:
106
  {{
107
- "reasoning_summary": "Detailed summary",
108
  "confidence_score": 0.85,
109
- "divergences": [ {{"variable": "var", "finding_a": "A", "finding_b": "B", "impact": "impact"}} ],
110
- "contradictions": [ {{"topic": "topic", "conflict": "conflict", "resolution_path": "path"}} ],
111
- "common_findings": ["finding 1", "finding 2"],
112
- "research_gaps": [ {{"description": "gap", "importance_score": 0.9, "related_variables": ["v1"], "suggested_investigation": "investigation", "source_documents": ["doc_id"], "citations": ["cit"]}} ],
113
- "counter_hypotheses": [ {{"hypothesis": "hyp", "rationale": "rat", "potential_bias": "bias", "validation_experiment": "exp", "confidence_against": 0.8, "citations": ["cit"]}} ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  "protocol": {{
115
- "title": "Protocol title", "hypothesis": "hyp", "objective": "obj", "expected_outcomes": "outcomes",
116
- "statistical_analysis_plan": "plan", "success_criteria": ["crit"], "estimated_duration_days": 30.0,
117
- "estimated_budget_usd": 10000.0, "resource_optimization": "opt", "material_constraints": "const",
118
- "alternative_approaches": ["alt"], "risk_assessment": {{"risk": "mitigation"}},
119
- "variables": [ {{"name": "v", "type": "independent", "measurement_unit": "unit", "measurement_method": "method", "possible_values": ["val"]}} ],
120
- "steps": [ {{"description": "step", "duration_hours": 2.5, "materials": ["mat"], "critical_parameters": ["param"], "validation_criteria": "crit", "risk_level": "medium", "contingency_plan": "plan"}} ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }},
122
- "recommendations": ["rec 1"]
 
 
 
123
  }}
124
 
125
- [FINAL OUTPUT STEP]
126
- Take your time to deeply analyze the documents inside a <think> block. After your analysis is complete, you MUST output the requested JSON object exactly. To ensure correct parsing, please enclose your final JSON object between [RESULT] and [/RESULT] tags. Do not truncate the JSON. NEVER output `... JSON ...`.
 
 
 
 
 
 
 
127
  """
128
 
129
  # 5. Appel au modèle (on met tout dans le message humain pour plus d'impact)
@@ -149,54 +221,104 @@ Take your time to deeply analyze the documents inside a <think> block. After you
149
  # NETTOYAGE MANUEL DU JSON (Crucial pour les modèles "Thinking")
150
  raw_content = response.content
151
 
152
- # 5. NETTOYAGE ET RÉPARATION DU JSON
153
  raw_content = response.content
154
  import re
155
  import json
156
 
157
- # 1. Tentative d'extraction via bloc markdown standard (très fiable pour les LLMs)
 
 
 
158
  json_block_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', raw_content, re.DOTALL)
159
 
160
  if json_block_match:
161
  clean_json = json_block_match.group(1).strip()
 
162
  else:
163
  # 2. Tentative d'extraction via balises [RESULT]
164
- tag_match = re.search(r'\[RESULT\]\s*(\{.*?\})\s*\[/RESULT\]', raw_content, re.DOTALL)
165
- if tag_match:
166
- clean_json = tag_match.group(1).strip()
 
167
  else:
168
- # 3. Recherche du bloc JSON le plus large possible (du premier { au dernier })
169
- # Cela ignore les petits {} aléatoires dans le texte de réflexion.
170
  start_idx = raw_content.find('{')
171
  end_idx = raw_content.rfind('}')
172
  if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
173
  clean_json = raw_content[start_idx:end_idx + 1]
174
-
 
 
 
175
  if not clean_json:
176
- logger.error(f"No JSON block found in content: {raw_content[:100]}...")
177
  raise ValueError("The AI model did not return a valid scientific result block. Please try again.")
178
 
179
- # 3. Nettoyage final des résidus de Markdown et erreurs LLM communes
180
  clean_json = clean_json.replace("```json", "").replace("```", "").strip()
181
  clean_json = re.sub(r'^\s*//.*$', '', clean_json, flags=re.MULTILINE)
182
  clean_json = re.sub(r',\s*([\]\}])', r'\1', clean_json)
183
 
 
 
 
184
  try:
185
  k2_analysis = json.loads(clean_json)
186
- except Exception as e:
 
187
  logger.error(f"JSON.LOADS failed: {e}")
188
- # Tentative ultime : nettoyage markdown et correction d'erreurs courantes LLM
 
 
189
  try:
190
  clean_json_fixed = clean_json.replace("```json", "").replace("```", "").strip()
 
191
  # Fix single quotes around keys: {'key': ...} -> {"key": ...}
192
  clean_json_fixed = re.sub(r"([{,]\s*)'([^']+)'(\s*:)", r'\1"\2"\3', clean_json_fixed)
 
193
  # Fix unquoted keys: {key: ...} -> {"key": ...}
194
  clean_json_fixed = re.sub(r'([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)', r'\1"\2"\3', clean_json_fixed)
195
- # Try to parse again
 
 
 
 
196
  k2_analysis = json.loads(clean_json_fixed)
197
- except Exception as e2:
198
- logger.error(f"All JSON parsing attempts failed. Error: {e2}. Cleaned JSON was: {clean_json[:500]}...")
199
- raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
  # 6. Conversion en schémas internes
202
  comparative_analysis = self._convert_k2_to_comparative_analysis(k2_analysis, request.documents)
 
87
 
88
  parser = JsonOutputParser()
89
 
90
+ # 4. Prompt autoritaire pour éviter la paresse (Laziness) - Version améliorée
91
  instruction_prompt = f"""[SCIENTIFIC MISSION]
92
  Perform an EXHAUSTIVE and DETAILED comparative analysis of the attached documents.
93
  CRITICAL RULES:
 
97
  - Your output must be a single, complete, and valid JSON object exactly matching the schema below.
98
  - You MUST populate ALL fields with complete, real text. Do NOT abbreviate the JSON output.
99
  - NEVER write `... JSON ...` or `{{"...": "..."}}`. You must write out the full, complete JSON object.
100
+ - IMPORTANT: Use DOUBLE QUOTES for all strings and keys. Do NOT use single quotes.
101
+ - IMPORTANT: Do NOT add comments or extra text outside the JSON structure.
102
 
103
  [DOCUMENTS TO ANALYZE]
104
  {context}
105
 
106
+ [REQUIRED SCHEMA - COPY THIS EXACTLY]
107
  You must return a JSON object with the following structure:
108
  {{
109
+ "reasoning_summary": "Detailed summary of your analysis process and key findings",
110
  "confidence_score": 0.85,
111
+ "divergences": [
112
+ {{
113
+ "variable": "specific_variable_name",
114
+ "finding_a": "Complete finding from document A with citation",
115
+ "finding_b": "Complete finding from document B with citation",
116
+ "impact": "Detailed explanation of the scientific impact of this divergence"
117
+ }}
118
+ ],
119
+ "contradictions": [
120
+ {{
121
+ "topic": "specific_topic_of_contradiction",
122
+ "conflict": "Detailed description of the conflicting findings",
123
+ "resolution_path": "Step-by-step approach to resolve this contradiction experimentally"
124
+ }}
125
+ ],
126
+ "common_findings": [
127
+ "First common finding with full explanation",
128
+ "Second common finding with full explanation"
129
+ ],
130
+ "research_gaps": [
131
+ {{
132
+ "description": "Detailed description of the research gap identified",
133
+ "importance_score": 0.9,
134
+ "related_variables": ["variable1", "variable2"],
135
+ "suggested_investigation": "Detailed experimental approach to address this gap",
136
+ "source_documents": ["Document title or citation"],
137
+ "citations": ["(Author, Year)"]
138
+ }}
139
+ ],
140
+ "counter_hypotheses": [
141
+ {{
142
+ "hypothesis": "Alternative hypothesis that contradicts common findings",
143
+ "rationale": "Scientific rationale for considering this counter hypothesis",
144
+ "potential_bias": "Potential sources of bias in the original studies",
145
+ "validation_experiment": "Detailed experimental design to test this hypothesis",
146
+ "confidence_against": 0.8,
147
+ "citations": ["(Author, Year)"]
148
+ }}
149
+ ],
150
  "protocol": {{
151
+ "title": "Complete protocol title describing the experimental approach",
152
+ "hypothesis": "Clear, testable hypothesis statement",
153
+ "objective": "Specific objectives of the experimental protocol",
154
+ "expected_outcomes": "Expected results and their scientific significance",
155
+ "statistical_analysis_plan": "Detailed statistical analysis approach",
156
+ "success_criteria": ["Criterion 1", "Criterion 2"],
157
+ "estimated_duration_days": 30.0,
158
+ "estimated_budget_usd": 10000.0,
159
+ "resource_optimization": "Strategy for optimizing resource usage",
160
+ "material_constraints": "Any material or equipment constraints",
161
+ "alternative_approaches": ["Alternative method 1", "Alternative method 2"],
162
+ "risk_assessment": {{"risk_type": "Detailed mitigation strategy"}},
163
+ "variables": [
164
+ {{
165
+ "name": "variable_name",
166
+ "type": "independent",
167
+ "measurement_unit": "unit_of_measurement",
168
+ "measurement_method": "Detailed measurement procedure",
169
+ "possible_values": ["value1", "value2"]
170
+ }}
171
+ ],
172
+ "steps": [
173
+ {{
174
+ "description": "Detailed step description with all parameters",
175
+ "duration_hours": 2.5,
176
+ "materials": ["Material 1", "Material 2"],
177
+ "critical_parameters": ["Parameter 1", "Parameter 2"],
178
+ "validation_criteria": "How to validate this step was performed correctly",
179
+ "risk_level": "medium",
180
+ "contingency_plan": "What to do if this step fails"
181
+ }}
182
+ ]
183
  }},
184
+ "recommendations": [
185
+ "First detailed recommendation for future research",
186
+ "Second detailed recommendation for future research"
187
+ ]
188
  }}
189
 
190
+ [FINAL OUTPUT INSTRUCTIONS]
191
+ 1. Think step-by-step inside a <think> block about your analysis
192
+ 2. After your analysis is complete, output ONLY the JSON object
193
+ 3. Do NOT include any text before or after the JSON
194
+ 4. Ensure the JSON is valid and parseable
195
+ 5. Use double quotes for all strings and keys
196
+ 6. Do not use single quotes anywhere in the JSON
197
+
198
+ [RESULT]
199
  """
200
 
201
  # 5. Appel au modèle (on met tout dans le message humain pour plus d'impact)
 
221
  # NETTOYAGE MANUEL DU JSON (Crucial pour les modèles "Thinking")
222
  raw_content = response.content
223
 
224
+ # 5. NETTOYAGE ET RÉPARATION DU JSON (Version améliorée)
225
  raw_content = response.content
226
  import re
227
  import json
228
 
229
+ logger.info(f"Raw K2 response length: {len(raw_content)}")
230
+ logger.debug(f"Raw K2 response preview: {raw_content[:500]}...")
231
+
232
+ # 1. Tentative d'extraction via bloc markdown standard
233
  json_block_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', raw_content, re.DOTALL)
234
 
235
  if json_block_match:
236
  clean_json = json_block_match.group(1).strip()
237
+ logger.info("JSON extracted from markdown block")
238
  else:
239
  # 2. Tentative d'extraction via balises [RESULT]
240
+ result_match = re.search(r'\[RESULT\]\s*(\{.*?\})\s*$', raw_content, re.DOTALL)
241
+ if result_match:
242
+ clean_json = result_match.group(1).strip()
243
+ logger.info("JSON extracted from [RESULT] tag")
244
  else:
245
+ # 3. Recherche du bloc JSON le plus large possible
 
246
  start_idx = raw_content.find('{')
247
  end_idx = raw_content.rfind('}')
248
  if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
249
  clean_json = raw_content[start_idx:end_idx + 1]
250
+ logger.info("JSON extracted by finding outermost braces")
251
+ else:
252
+ clean_json = ""
253
+
254
  if not clean_json:
255
+ logger.error(f"No JSON block found in content. Full content: {raw_content}")
256
  raise ValueError("The AI model did not return a valid scientific result block. Please try again.")
257
 
258
+ # 4. Nettoyage final des résidus de Markdown et erreurs LLM communes
259
  clean_json = clean_json.replace("```json", "").replace("```", "").strip()
260
  clean_json = re.sub(r'^\s*//.*$', '', clean_json, flags=re.MULTILINE)
261
  clean_json = re.sub(r',\s*([\]\}])', r'\1', clean_json)
262
 
263
+ logger.info(f"Cleaned JSON length: {len(clean_json)}")
264
+ logger.debug(f"Cleaned JSON preview: {clean_json[:200]}...")
265
+
266
  try:
267
  k2_analysis = json.loads(clean_json)
268
+ logger.info("JSON parsing successful")
269
+ except json.JSONDecodeError as e:
270
  logger.error(f"JSON.LOADS failed: {e}")
271
+ logger.error(f"Failed JSON content: {clean_json}")
272
+
273
+ # Tentative ultime : nettoyage avancé et correction d'erreurs courantes LLM
274
  try:
275
  clean_json_fixed = clean_json.replace("```json", "").replace("```", "").strip()
276
+
277
  # Fix single quotes around keys: {'key': ...} -> {"key": ...}
278
  clean_json_fixed = re.sub(r"([{,]\s*)'([^']+)'(\s*:)", r'\1"\2"\3', clean_json_fixed)
279
+
280
  # Fix unquoted keys: {key: ...} -> {"key": ...}
281
  clean_json_fixed = re.sub(r'([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)', r'\1"\2"\3', clean_json_fixed)
282
+
283
+ # Fix trailing commas before closing braces/brackets
284
+ clean_json_fixed = re.sub(r',\s*(\}|\])', r'\1', clean_json_fixed)
285
+
286
+ logger.info("Attempting to parse with fixes applied")
287
  k2_analysis = json.loads(clean_json_fixed)
288
+ logger.info("JSON parsing successful after fixes")
289
+
290
+ except json.JSONDecodeError as e2:
291
+ logger.error(f"All JSON parsing attempts failed. Error: {e2}")
292
+ logger.error(f"Final cleaned JSON was: {clean_json_fixed[:1000]}...")
293
+
294
+ # Si tout échoue, créer un résultat par défaut avec les informations disponibles
295
+ logger.warning("Creating fallback analysis result due to JSON parsing failure")
296
+ k2_analysis = {
297
+ "reasoning_summary": f"Analysis completed but JSON parsing failed. Raw response length: {len(raw_content)} characters. Error: {str(e2)}",
298
+ "confidence_score": 0.5,
299
+ "divergences": [],
300
+ "contradictions": [],
301
+ "common_findings": ["Analysis attempted but result parsing failed"],
302
+ "research_gaps": [],
303
+ "counter_hypotheses": [],
304
+ "protocol": {
305
+ "title": "Analysis Failed - Manual Review Required",
306
+ "hypothesis": "Unable to parse AI response",
307
+ "objective": "Manual review of raw AI output needed",
308
+ "expected_outcomes": "Manual analysis required",
309
+ "statistical_analysis_plan": "TBD",
310
+ "success_criteria": ["Manual review completed"],
311
+ "estimated_duration_days": 1.0,
312
+ "estimated_budget_usd": 0.0,
313
+ "resource_optimization": "N/A",
314
+ "material_constraints": "N/A",
315
+ "alternative_approaches": ["Manual analysis"],
316
+ "risk_assessment": {"parsing_failure": "Manual intervention required"},
317
+ "variables": [],
318
+ "steps": []
319
+ },
320
+ "recommendations": ["Review raw AI response manually", "Consider retrying analysis"]
321
+ }
322
 
323
  # 6. Conversion en schémas internes
324
  comparative_analysis = self._convert_k2_to_comparative_analysis(k2_analysis, request.documents)
docker-compose.yml CHANGED
@@ -11,7 +11,7 @@ services:
11
  environment:
12
  POSTGRES_DB: scoinvestigator
13
  POSTGRES_USER: ${POSTGRES_USER:-user}
14
- POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-onion123}
15
  ports:
16
  - "5432:5432"
17
  volumes:
@@ -65,7 +65,7 @@ services:
65
  ports:
66
  - "8000:7860"
67
  environment:
68
- DATABASE_URL: postgresql://${POSTGRES_USER:-user}:${POSTGRES_PASSWORD:-onion123}@postgres:5432/scoinvestigator
69
  VECTOR_DB_URL: http://qdrant:6333
70
  QDRANT_API_KEY: ${QDRANT_API_KEY:-your-secret-key}
71
  CELERY_BROKER_URL: redis://redis:6379/0
@@ -73,7 +73,7 @@ services:
73
  OPENAI_API_KEY: ${OPENAI_API_KEY}
74
  K2_THINK_API_KEY: ${K2_THINK_API_KEY}
75
  K2_THINK_API_URL: ${K2_THINK_API_URL:-https://api.k2think.com/v1}
76
- SECRET_KEY: ${SECRET_KEY:-dev-key-change-in-production}
77
  ALGORITHM: ${ALGORITHM:-HS256}
78
  ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-30}
79
  LOG_LEVEL: ${LOG_LEVEL:-INFO}
 
11
  environment:
12
  POSTGRES_DB: scoinvestigator
13
  POSTGRES_USER: ${POSTGRES_USER:-user}
14
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-CHANGE_THIS_PASSWORD}
15
  ports:
16
  - "5432:5432"
17
  volumes:
 
65
  ports:
66
  - "8000:7860"
67
  environment:
68
+ DATABASE_URL: postgresql://${POSTGRES_USER:-user}:${POSTGRES_PASSWORD:-CHANGE_THIS_PASSWORD}@postgres:5432/scoinvestigator
69
  VECTOR_DB_URL: http://qdrant:6333
70
  QDRANT_API_KEY: ${QDRANT_API_KEY:-your-secret-key}
71
  CELERY_BROKER_URL: redis://redis:6379/0
 
73
  OPENAI_API_KEY: ${OPENAI_API_KEY}
74
  K2_THINK_API_KEY: ${K2_THINK_API_KEY}
75
  K2_THINK_API_URL: ${K2_THINK_API_URL:-https://api.k2think.com/v1}
76
+ SECRET_KEY: ${SECRET_KEY:-CHANGE_THIS_TO_A_SECURE_RANDOM_STRING_32_CHARS_MINIMUM}
77
  ALGORITHM: ${ALGORITHM:-HS256}
78
  ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-30}
79
  LOG_LEVEL: ${LOG_LEVEL:-INFO}