GuXiaopeng commited on
Commit
99198a6
·
verified ·
1 Parent(s): 16a6c6c

Update core/pipeline_agent.py

Browse files
Files changed (1) hide show
  1. core/pipeline_agent.py +63 -41
core/pipeline_agent.py CHANGED
@@ -68,6 +68,28 @@ class SSciPipelineAgent:
68
  except:
69
  self.tokenizer = tiktoken.get_encoding("gpt2")
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  def export_state(self):
72
  with self.lock:
73
  return {
@@ -151,29 +173,31 @@ class SSciPipelineAgent:
151
  if self.global_memory["method_summary"]: memory_parts.append(f"【METHODOLOGICAL SUMMARY】\n{self.global_memory['method_summary']}")
152
 
153
  golden_meta = self.global_memory.get("core_golden_metadata", [])
154
- if golden_meta and section_name:
 
155
  lower_sec = section_name.lower()
156
  injected_data = []
157
 
158
  if any(kw in lower_sec for kw in ["introduction", "background"]):
159
  for p in golden_meta:
160
- if p.get('future_gaps'): injected_data.append(f"[{p['title']}] Future Gaps: {p['future_gaps']}")
161
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE GAPS (FOR RESEARCH GAP FORMULATION)】\n" + "\n".join(injected_data))
162
 
163
  elif any(kw in lower_sec for kw in ["literature", "theor", "hypothesis", "review"]):
164
  for p in golden_meta:
165
- if p.get('theoretical_lens'): injected_data.append(f"[{p['title']}] Theoretical Lens: {p['theoretical_lens']}")
166
- if p.get('causal_pathways'): injected_data.append(f"[{p['title']}] Pathways: {p['causal_pathways']}")
 
167
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE THEORIES & PATHWAYS (USE FOR HYPOTHESIS DEDUCTION)】\n" + "\n".join(injected_data))
168
 
169
  elif any(kw in lower_sec for kw in ["method", "measure", "data", "sample"]):
170
  for p in golden_meta:
171
- if p.get('variables_and_measurements'): injected_data.append(f"[{p['title']}] Measurements: {p['variables_and_measurements']}")
172
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE MEASUREMENTS (USE FOR SCALE ADAPTATION DEFENSE)】\n" + "\n".join(injected_data))
173
 
174
  elif any(kw in lower_sec for kw in ["discussion", "conclusion", "implication", "result", "analysis"]):
175
  for p in golden_meta:
176
- if p.get('causal_pathways'): injected_data.append(f"[{p['title']}] Confirmed Pathways: {p['causal_pathways']}")
177
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE FINDINGS (COMPARE OUR RESULTS AGAINST THESE)】\n" + "\n".join(injected_data))
178
 
179
  full = "\n\n".join(memory_parts)
@@ -291,7 +315,6 @@ class SSciPipelineAgent:
291
  try:
292
  response = self.call_deepseek(sys_prompt, user_prompt, max_tokens=300)
293
  clean_resp = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL).strip()
294
- clean_resp = re.sub(r'^```.*|```$', '', clean_resp, flags=re.MULTILINE).strip()
295
  if '||' in clean_resp:
296
  headings = [h.strip() for h in clean_resp.split('||') if h.strip()]
297
  if len(headings) >= 3: return headings
@@ -305,11 +328,10 @@ class SSciPipelineAgent:
305
  user_prompt = f"Target Section: {section_name}\nAnalyze the reference text:\n<REFERENCE_TEXT>\n{reference_text[:10000]}\n</REFERENCE_TEXT>\nOutput ONLY a valid JSON object: {{\"tone_and_style\": \"...\", \"reporting_structure\": \"...\", \"few_shot_snippet\": \"...\"}}"
306
  for attempt in range(3):
307
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.2)
308
- clean = re.sub(r'^```json\s*|\s*```$', '', response.strip(), flags=re.IGNORECASE | re.MULTILINE)
309
- try:
310
- result = json.loads(clean)
311
- if "few_shot_snippet" in result: return result
312
- except: time.sleep(1)
313
  return {"tone_and_style": "objective academic tone", "reporting_structure": "standard empirical reporting", "few_shot_snippet": ""}
314
 
315
  def parse_user_core_literature(self, user_lit_text):
@@ -339,13 +361,23 @@ class SSciPipelineAgent:
339
  for attempt in range(2):
340
  try:
341
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.1, model_name="deepseek-chat")
342
- clean = re.sub(r'^```json\s*|\s*```$', '', response.strip(), flags=re.IGNORECASE | re.MULTILINE)
343
- papers = json.loads(clean)
 
 
 
 
 
 
 
 
 
344
 
345
  self.global_memory["core_golden_metadata"] = papers
346
 
347
  rag_context = []
348
  for paper in papers:
 
349
  title = paper.get("title", "User Provided Document")
350
  year = paper.get("year", "n.d.")
351
  authors = paper.get("authors", ["User, Uploaded"])
@@ -396,35 +428,26 @@ class SSciPipelineAgent:
396
 
397
  def generate_outline(self, topic, keywords, synthesis_result):
398
  sys_prompt = "You are a senior academic journal reviewer. Design a strict empirical research paper outline."
399
- user_prompt = f"""Topic: {topic}\nKeywords: {keywords}\nLiterature Synthesis & Gaps:\n{synthesis_result}\n\nConstruct a detailed empirical outline. Your response must be a single valid JSON object: {{"en_topic": "...", "en_keywords": "...", "structure": [{{"section": "...", "weight": 0.15, "is_method_or_result": false}}]}}.
400
- CRITICAL: The 'structure' array MUST ONLY contain main body sections. DO NOT include Abstract, Keywords, Acknowledgments, or References in the structure array.
401
- Do not include any other text."""
402
  for attempt in range(3):
403
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.2)
404
- clean = re.sub(r'^```json\s*|\s*```$', '', response.strip(), flags=re.IGNORECASE | re.MULTILINE)
405
- try:
406
- plan = json.loads(clean)
407
- if "structure" in plan: return plan
408
- except Exception: time.sleep(2)
409
- raise ValueError("Outline Generation Failed. Invalid JSON structure.")
410
 
411
  def generate_search_queries(self, topic, keywords):
412
  sys_prompt = "You are an expert academic librarian. Generate English search queries."
413
  user_prompt = f"""Topic: {topic}\nKeywords: {keywords}\nGenerate exactly 3 concise English search queries. Return ONLY a valid JSON array of strings."""
414
  for attempt in range(3):
415
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.1 + attempt * 0.1)
416
- clean = re.sub(r'^```json\s*|\s*```$', '', response.strip(), flags=re.IGNORECASE | re.MULTILINE)
417
- try:
418
- queries = json.loads(clean)
419
- if isinstance(queries, list) and len(queries) > 0:
420
- final_queries = [str(q).strip() for q in queries if self._is_english_dominant(str(q).strip())]
421
- if final_queries: return final_queries[:3]
422
- except:
423
- fallback_queries = re.findall(r'"([a-zA-Z\s\-]+)"', response)
424
- if fallback_queries:
425
- final_queries = [q.strip() for q in fallback_queries if self._is_english_dominant(q.strip())]
426
- if final_queries: return final_queries[:3]
427
  time.sleep(1)
 
428
  combined_text = f"{topic} {keywords}"
429
  english_chunks = re.findall(r'[a-zA-Z0-9\s\-]{4,}', combined_text)
430
  clean_chunks = [chunk.strip() for chunk in english_chunks if len(chunk.strip()) >= 4 and self._is_english_dominant(chunk.strip())]
@@ -463,13 +486,12 @@ class SSciPipelineAgent:
463
  else:
464
  sys_prompt = "You are an expert academic librarian. The previous literature search yielded insufficient results. We must broaden the search scope."
465
  user_prompt = f"Original Topic: {topic}\nOriginal Keywords: {keywords}\nAlready Used Queries: {list(all_used_terms)}\nGenerate exactly 3 NEW, BROADER, or RELATED English search queries (max 3-4 words each) to expand the literature search. Do NOT repeat previous queries. Return ONLY a valid JSON array of strings."
466
- try:
467
- response = self.call_deepseek(sys_prompt, user_prompt, temp=0.5)
468
- clean = re.sub(r'^```json\s*|\s*```$', '', response.strip(), flags=re.IGNORECASE | re.MULTILINE)
469
- queries = json.loads(clean)
470
- if isinstance(queries, list): search_terms = [str(q).strip() for q in queries if self._is_english_dominant(str(q).strip())][:3]
471
- else: search_terms = []
472
- except:
473
  fallback_queries = re.findall(r'"([a-zA-Z\s\-]+)"', response)
474
  search_terms = [q.strip() for q in fallback_queries if self._is_english_dominant(q.strip())][:3]
475
 
 
68
  except:
69
  self.tokenizer = tiktoken.get_encoding("gpt2")
70
 
71
+ # 🚨 新增:无敌 JSON 提取器。杜绝大模型废话导致的解析崩溃
72
+ def _extract_json_from_text(self, text):
73
+ if not text: return None
74
+ text = text.strip()
75
+ try:
76
+ start_dict = text.find('{')
77
+ end_dict = text.rfind('}')
78
+ start_list = text.find('[')
79
+ end_list = text.rfind(']')
80
+
81
+ dict_len = end_dict - start_dict if start_dict != -1 and end_dict != -1 else -1
82
+ list_len = end_list - start_list if start_list != -1 and end_list != -1 else -1
83
+
84
+ if dict_len == -1 and list_len == -1: return None
85
+
86
+ if dict_len > list_len:
87
+ return json.loads(text[start_dict:end_dict+1])
88
+ else:
89
+ return json.loads(text[start_list:end_list+1])
90
+ except Exception:
91
+ return None
92
+
93
  def export_state(self):
94
  with self.lock:
95
  return {
 
173
  if self.global_memory["method_summary"]: memory_parts.append(f"【METHODOLOGICAL SUMMARY】\n{self.global_memory['method_summary']}")
174
 
175
  golden_meta = self.global_memory.get("core_golden_metadata", [])
176
+ # 🚨 类型安全防护:确保 golden_meta 一定是 List,防止大模型意外包裹字典导致遍历崩溃
177
+ if isinstance(golden_meta, list) and section_name:
178
  lower_sec = section_name.lower()
179
  injected_data = []
180
 
181
  if any(kw in lower_sec for kw in ["introduction", "background"]):
182
  for p in golden_meta:
183
+ if isinstance(p, dict) and p.get('future_gaps'): injected_data.append(f"[{p.get('title', 'Ref')}] Future Gaps: {p['future_gaps']}")
184
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE GAPS (FOR RESEARCH GAP FORMULATION)】\n" + "\n".join(injected_data))
185
 
186
  elif any(kw in lower_sec for kw in ["literature", "theor", "hypothesis", "review"]):
187
  for p in golden_meta:
188
+ if isinstance(p, dict):
189
+ if p.get('theoretical_lens'): injected_data.append(f"[{p.get('title', 'Ref')}] Theoretical Lens: {p['theoretical_lens']}")
190
+ if p.get('causal_pathways'): injected_data.append(f"[{p.get('title', 'Ref')}] Pathways: {p['causal_pathways']}")
191
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE THEORIES & PATHWAYS (USE FOR HYPOTHESIS DEDUCTION)】\n" + "\n".join(injected_data))
192
 
193
  elif any(kw in lower_sec for kw in ["method", "measure", "data", "sample"]):
194
  for p in golden_meta:
195
+ if isinstance(p, dict) and p.get('variables_and_measurements'): injected_data.append(f"[{p.get('title', 'Ref')}] Measurements: {p['variables_and_measurements']}")
196
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE MEASUREMENTS (USE FOR SCALE ADAPTATION DEFENSE)】\n" + "\n".join(injected_data))
197
 
198
  elif any(kw in lower_sec for kw in ["discussion", "conclusion", "implication", "result", "analysis"]):
199
  for p in golden_meta:
200
+ if isinstance(p, dict) and p.get('causal_pathways'): injected_data.append(f"[{p.get('title', 'Ref')}] Confirmed Pathways: {p['causal_pathways']}")
201
  if injected_data: memory_parts.append("【TARGETED INJECTION: CORE LITERATURE FINDINGS (COMPARE OUR RESULTS AGAINST THESE)】\n" + "\n".join(injected_data))
202
 
203
  full = "\n\n".join(memory_parts)
 
315
  try:
316
  response = self.call_deepseek(sys_prompt, user_prompt, max_tokens=300)
317
  clean_resp = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL).strip()
 
318
  if '||' in clean_resp:
319
  headings = [h.strip() for h in clean_resp.split('||') if h.strip()]
320
  if len(headings) >= 3: return headings
 
328
  user_prompt = f"Target Section: {section_name}\nAnalyze the reference text:\n<REFERENCE_TEXT>\n{reference_text[:10000]}\n</REFERENCE_TEXT>\nOutput ONLY a valid JSON object: {{\"tone_and_style\": \"...\", \"reporting_structure\": \"...\", \"few_shot_snippet\": \"...\"}}"
329
  for attempt in range(3):
330
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.2)
331
+ result = self._extract_json_from_text(response)
332
+ if result and isinstance(result, dict) and "few_shot_snippet" in result:
333
+ return result
334
+ time.sleep(1)
 
335
  return {"tone_and_style": "objective academic tone", "reporting_structure": "standard empirical reporting", "few_shot_snippet": ""}
336
 
337
  def parse_user_core_literature(self, user_lit_text):
 
361
  for attempt in range(2):
362
  try:
363
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.1, model_name="deepseek-chat")
364
+ papers = self._extract_json_from_text(response)
365
+
366
+ if not papers: raise ValueError("Extracted JSON is null")
367
+
368
+ # 修复可能包裹在 dict 中的情况 (e.g., {"papers": [...]})
369
+ if isinstance(papers, dict):
370
+ for v in papers.values():
371
+ if isinstance(v, list):
372
+ papers = v
373
+ break
374
+ if not isinstance(papers, list): papers = [papers]
375
 
376
  self.global_memory["core_golden_metadata"] = papers
377
 
378
  rag_context = []
379
  for paper in papers:
380
+ if not isinstance(paper, dict): continue
381
  title = paper.get("title", "User Provided Document")
382
  year = paper.get("year", "n.d.")
383
  authors = paper.get("authors", ["User, Uploaded"])
 
428
 
429
  def generate_outline(self, topic, keywords, synthesis_result):
430
  sys_prompt = "You are a senior academic journal reviewer. Design a strict empirical research paper outline."
431
+ user_prompt = f"""Topic: {topic}\nKeywords: {keywords}\nLiterature Synthesis & Gaps:\n{synthesis_result}\n\nConstruct a detailed empirical outline. Your response must be a single valid JSON object: {{"en_topic": "...", "en_keywords": "...", "structure": [{{"section": "...", "weight": 0.15, "is_method_or_result": false}}]}}. Do not include any other text."""
 
 
432
  for attempt in range(3):
433
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.2)
434
+ plan = self._extract_json_from_text(response)
435
+ if plan and isinstance(plan, dict) and "structure" in plan:
436
+ return plan
437
+ time.sleep(2)
438
+ raise ValueError("Outline Generation Failed. Invalid JSON structure returned by model.")
 
439
 
440
  def generate_search_queries(self, topic, keywords):
441
  sys_prompt = "You are an expert academic librarian. Generate English search queries."
442
  user_prompt = f"""Topic: {topic}\nKeywords: {keywords}\nGenerate exactly 3 concise English search queries. Return ONLY a valid JSON array of strings."""
443
  for attempt in range(3):
444
  response = self.call_deepseek(sys_prompt, user_prompt, temp=0.1 + attempt * 0.1)
445
+ queries = self._extract_json_from_text(response)
446
+ if queries and isinstance(queries, list) and len(queries) > 0:
447
+ final_queries = [str(q).strip() for q in queries if self._is_english_dominant(str(q).strip())]
448
+ if final_queries: return final_queries[:3]
 
 
 
 
 
 
 
449
  time.sleep(1)
450
+
451
  combined_text = f"{topic} {keywords}"
452
  english_chunks = re.findall(r'[a-zA-Z0-9\s\-]{4,}', combined_text)
453
  clean_chunks = [chunk.strip() for chunk in english_chunks if len(chunk.strip()) >= 4 and self._is_english_dominant(chunk.strip())]
 
486
  else:
487
  sys_prompt = "You are an expert academic librarian. The previous literature search yielded insufficient results. We must broaden the search scope."
488
  user_prompt = f"Original Topic: {topic}\nOriginal Keywords: {keywords}\nAlready Used Queries: {list(all_used_terms)}\nGenerate exactly 3 NEW, BROADER, or RELATED English search queries (max 3-4 words each) to expand the literature search. Do NOT repeat previous queries. Return ONLY a valid JSON array of strings."
489
+ response = self.call_deepseek(sys_prompt, user_prompt, temp=0.5)
490
+ queries = self._extract_json_from_text(response)
491
+
492
+ if queries and isinstance(queries, list):
493
+ search_terms = [str(q).strip() for q in queries if self._is_english_dominant(str(q).strip())][:3]
494
+ else:
 
495
  fallback_queries = re.findall(r'"([a-zA-Z\s\-]+)"', response)
496
  search_terms = [q.strip() for q in fallback_queries if self._is_english_dominant(q.strip())][:3]
497