realtimex-deeptutor 0.5.0.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- realtimex_deeptutor/__init__.py +67 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/METADATA +1612 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/RECORD +276 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/WHEEL +5 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/entry_points.txt +2 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/licenses/LICENSE +661 -0
- realtimex_deeptutor-0.5.0.post1.dist-info/top_level.txt +2 -0
- src/__init__.py +40 -0
- src/agents/__init__.py +24 -0
- src/agents/base_agent.py +657 -0
- src/agents/chat/__init__.py +24 -0
- src/agents/chat/chat_agent.py +435 -0
- src/agents/chat/prompts/en/chat_agent.yaml +35 -0
- src/agents/chat/prompts/zh/chat_agent.yaml +35 -0
- src/agents/chat/session_manager.py +311 -0
- src/agents/co_writer/__init__.py +0 -0
- src/agents/co_writer/edit_agent.py +260 -0
- src/agents/co_writer/narrator_agent.py +423 -0
- src/agents/co_writer/prompts/en/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/en/narrator_agent.yaml +88 -0
- src/agents/co_writer/prompts/zh/edit_agent.yaml +113 -0
- src/agents/co_writer/prompts/zh/narrator_agent.yaml +88 -0
- src/agents/guide/__init__.py +16 -0
- src/agents/guide/agents/__init__.py +11 -0
- src/agents/guide/agents/chat_agent.py +104 -0
- src/agents/guide/agents/interactive_agent.py +223 -0
- src/agents/guide/agents/locate_agent.py +149 -0
- src/agents/guide/agents/summary_agent.py +150 -0
- src/agents/guide/guide_manager.py +500 -0
- src/agents/guide/prompts/en/chat_agent.yaml +41 -0
- src/agents/guide/prompts/en/interactive_agent.yaml +202 -0
- src/agents/guide/prompts/en/locate_agent.yaml +68 -0
- src/agents/guide/prompts/en/summary_agent.yaml +157 -0
- src/agents/guide/prompts/zh/chat_agent.yaml +41 -0
- src/agents/guide/prompts/zh/interactive_agent.yaml +626 -0
- src/agents/guide/prompts/zh/locate_agent.yaml +68 -0
- src/agents/guide/prompts/zh/summary_agent.yaml +157 -0
- src/agents/ideagen/__init__.py +12 -0
- src/agents/ideagen/idea_generation_workflow.py +426 -0
- src/agents/ideagen/material_organizer_agent.py +173 -0
- src/agents/ideagen/prompts/en/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/en/material_organizer.yaml +69 -0
- src/agents/ideagen/prompts/zh/idea_generation.yaml +187 -0
- src/agents/ideagen/prompts/zh/material_organizer.yaml +69 -0
- src/agents/question/__init__.py +24 -0
- src/agents/question/agents/__init__.py +18 -0
- src/agents/question/agents/generate_agent.py +381 -0
- src/agents/question/agents/relevance_analyzer.py +207 -0
- src/agents/question/agents/retrieve_agent.py +239 -0
- src/agents/question/coordinator.py +718 -0
- src/agents/question/example.py +109 -0
- src/agents/question/prompts/en/coordinator.yaml +75 -0
- src/agents/question/prompts/en/generate_agent.yaml +77 -0
- src/agents/question/prompts/en/relevance_analyzer.yaml +41 -0
- src/agents/question/prompts/en/retrieve_agent.yaml +32 -0
- src/agents/question/prompts/zh/coordinator.yaml +75 -0
- src/agents/question/prompts/zh/generate_agent.yaml +77 -0
- src/agents/question/prompts/zh/relevance_analyzer.yaml +39 -0
- src/agents/question/prompts/zh/retrieve_agent.yaml +30 -0
- src/agents/research/agents/__init__.py +23 -0
- src/agents/research/agents/decompose_agent.py +507 -0
- src/agents/research/agents/manager_agent.py +228 -0
- src/agents/research/agents/note_agent.py +180 -0
- src/agents/research/agents/rephrase_agent.py +263 -0
- src/agents/research/agents/reporting_agent.py +1333 -0
- src/agents/research/agents/research_agent.py +714 -0
- src/agents/research/data_structures.py +451 -0
- src/agents/research/main.py +188 -0
- src/agents/research/prompts/en/decompose_agent.yaml +89 -0
- src/agents/research/prompts/en/manager_agent.yaml +24 -0
- src/agents/research/prompts/en/note_agent.yaml +121 -0
- src/agents/research/prompts/en/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/en/reporting_agent.yaml +380 -0
- src/agents/research/prompts/en/research_agent.yaml +173 -0
- src/agents/research/prompts/zh/decompose_agent.yaml +89 -0
- src/agents/research/prompts/zh/manager_agent.yaml +24 -0
- src/agents/research/prompts/zh/note_agent.yaml +121 -0
- src/agents/research/prompts/zh/rephrase_agent.yaml +58 -0
- src/agents/research/prompts/zh/reporting_agent.yaml +380 -0
- src/agents/research/prompts/zh/research_agent.yaml +173 -0
- src/agents/research/research_pipeline.py +1309 -0
- src/agents/research/utils/__init__.py +60 -0
- src/agents/research/utils/citation_manager.py +799 -0
- src/agents/research/utils/json_utils.py +98 -0
- src/agents/research/utils/token_tracker.py +297 -0
- src/agents/solve/__init__.py +80 -0
- src/agents/solve/analysis_loop/__init__.py +14 -0
- src/agents/solve/analysis_loop/investigate_agent.py +414 -0
- src/agents/solve/analysis_loop/note_agent.py +190 -0
- src/agents/solve/main_solver.py +862 -0
- src/agents/solve/memory/__init__.py +34 -0
- src/agents/solve/memory/citation_memory.py +353 -0
- src/agents/solve/memory/investigate_memory.py +226 -0
- src/agents/solve/memory/solve_memory.py +340 -0
- src/agents/solve/prompts/en/analysis_loop/investigate_agent.yaml +55 -0
- src/agents/solve/prompts/en/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/en/solve_loop/manager_agent.yaml +67 -0
- src/agents/solve/prompts/en/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/en/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/en/solve_loop/solve_agent.yaml +75 -0
- src/agents/solve/prompts/en/solve_loop/tool_agent.yaml +38 -0
- src/agents/solve/prompts/zh/analysis_loop/investigate_agent.yaml +53 -0
- src/agents/solve/prompts/zh/analysis_loop/note_agent.yaml +54 -0
- src/agents/solve/prompts/zh/solve_loop/manager_agent.yaml +66 -0
- src/agents/solve/prompts/zh/solve_loop/precision_answer_agent.yaml +62 -0
- src/agents/solve/prompts/zh/solve_loop/response_agent.yaml +90 -0
- src/agents/solve/prompts/zh/solve_loop/solve_agent.yaml +76 -0
- src/agents/solve/prompts/zh/solve_loop/tool_agent.yaml +41 -0
- src/agents/solve/solve_loop/__init__.py +22 -0
- src/agents/solve/solve_loop/citation_manager.py +74 -0
- src/agents/solve/solve_loop/manager_agent.py +274 -0
- src/agents/solve/solve_loop/precision_answer_agent.py +96 -0
- src/agents/solve/solve_loop/response_agent.py +301 -0
- src/agents/solve/solve_loop/solve_agent.py +325 -0
- src/agents/solve/solve_loop/tool_agent.py +470 -0
- src/agents/solve/utils/__init__.py +64 -0
- src/agents/solve/utils/config_validator.py +313 -0
- src/agents/solve/utils/display_manager.py +223 -0
- src/agents/solve/utils/error_handler.py +363 -0
- src/agents/solve/utils/json_utils.py +98 -0
- src/agents/solve/utils/performance_monitor.py +407 -0
- src/agents/solve/utils/token_tracker.py +541 -0
- src/api/__init__.py +0 -0
- src/api/main.py +240 -0
- src/api/routers/__init__.py +1 -0
- src/api/routers/agent_config.py +69 -0
- src/api/routers/chat.py +296 -0
- src/api/routers/co_writer.py +337 -0
- src/api/routers/config.py +627 -0
- src/api/routers/dashboard.py +18 -0
- src/api/routers/guide.py +337 -0
- src/api/routers/ideagen.py +436 -0
- src/api/routers/knowledge.py +821 -0
- src/api/routers/notebook.py +247 -0
- src/api/routers/question.py +537 -0
- src/api/routers/research.py +394 -0
- src/api/routers/settings.py +164 -0
- src/api/routers/solve.py +305 -0
- src/api/routers/system.py +252 -0
- src/api/run_server.py +61 -0
- src/api/utils/history.py +172 -0
- src/api/utils/log_interceptor.py +21 -0
- src/api/utils/notebook_manager.py +415 -0
- src/api/utils/progress_broadcaster.py +72 -0
- src/api/utils/task_id_manager.py +100 -0
- src/config/__init__.py +0 -0
- src/config/accessors.py +18 -0
- src/config/constants.py +34 -0
- src/config/defaults.py +18 -0
- src/config/schema.py +38 -0
- src/config/settings.py +50 -0
- src/core/errors.py +62 -0
- src/knowledge/__init__.py +23 -0
- src/knowledge/add_documents.py +606 -0
- src/knowledge/config.py +65 -0
- src/knowledge/example_add_documents.py +236 -0
- src/knowledge/extract_numbered_items.py +1039 -0
- src/knowledge/initializer.py +621 -0
- src/knowledge/kb.py +22 -0
- src/knowledge/manager.py +782 -0
- src/knowledge/progress_tracker.py +182 -0
- src/knowledge/start_kb.py +535 -0
- src/logging/__init__.py +103 -0
- src/logging/adapters/__init__.py +17 -0
- src/logging/adapters/lightrag.py +184 -0
- src/logging/adapters/llamaindex.py +141 -0
- src/logging/config.py +80 -0
- src/logging/handlers/__init__.py +20 -0
- src/logging/handlers/console.py +75 -0
- src/logging/handlers/file.py +201 -0
- src/logging/handlers/websocket.py +127 -0
- src/logging/logger.py +709 -0
- src/logging/stats/__init__.py +16 -0
- src/logging/stats/llm_stats.py +179 -0
- src/services/__init__.py +56 -0
- src/services/config/__init__.py +61 -0
- src/services/config/knowledge_base_config.py +210 -0
- src/services/config/loader.py +260 -0
- src/services/config/unified_config.py +603 -0
- src/services/embedding/__init__.py +45 -0
- src/services/embedding/adapters/__init__.py +22 -0
- src/services/embedding/adapters/base.py +106 -0
- src/services/embedding/adapters/cohere.py +127 -0
- src/services/embedding/adapters/jina.py +99 -0
- src/services/embedding/adapters/ollama.py +116 -0
- src/services/embedding/adapters/openai_compatible.py +96 -0
- src/services/embedding/client.py +159 -0
- src/services/embedding/config.py +156 -0
- src/services/embedding/provider.py +119 -0
- src/services/llm/__init__.py +152 -0
- src/services/llm/capabilities.py +313 -0
- src/services/llm/client.py +302 -0
- src/services/llm/cloud_provider.py +530 -0
- src/services/llm/config.py +200 -0
- src/services/llm/error_mapping.py +103 -0
- src/services/llm/exceptions.py +152 -0
- src/services/llm/factory.py +450 -0
- src/services/llm/local_provider.py +347 -0
- src/services/llm/providers/anthropic.py +95 -0
- src/services/llm/providers/base_provider.py +93 -0
- src/services/llm/providers/open_ai.py +83 -0
- src/services/llm/registry.py +71 -0
- src/services/llm/telemetry.py +40 -0
- src/services/llm/types.py +27 -0
- src/services/llm/utils.py +333 -0
- src/services/prompt/__init__.py +25 -0
- src/services/prompt/manager.py +206 -0
- src/services/rag/__init__.py +64 -0
- src/services/rag/components/__init__.py +29 -0
- src/services/rag/components/base.py +59 -0
- src/services/rag/components/chunkers/__init__.py +18 -0
- src/services/rag/components/chunkers/base.py +34 -0
- src/services/rag/components/chunkers/fixed.py +71 -0
- src/services/rag/components/chunkers/numbered_item.py +94 -0
- src/services/rag/components/chunkers/semantic.py +97 -0
- src/services/rag/components/embedders/__init__.py +14 -0
- src/services/rag/components/embedders/base.py +32 -0
- src/services/rag/components/embedders/openai.py +63 -0
- src/services/rag/components/indexers/__init__.py +18 -0
- src/services/rag/components/indexers/base.py +35 -0
- src/services/rag/components/indexers/graph.py +172 -0
- src/services/rag/components/indexers/lightrag.py +156 -0
- src/services/rag/components/indexers/vector.py +146 -0
- src/services/rag/components/parsers/__init__.py +18 -0
- src/services/rag/components/parsers/base.py +35 -0
- src/services/rag/components/parsers/markdown.py +52 -0
- src/services/rag/components/parsers/pdf.py +115 -0
- src/services/rag/components/parsers/text.py +86 -0
- src/services/rag/components/retrievers/__init__.py +18 -0
- src/services/rag/components/retrievers/base.py +34 -0
- src/services/rag/components/retrievers/dense.py +200 -0
- src/services/rag/components/retrievers/hybrid.py +164 -0
- src/services/rag/components/retrievers/lightrag.py +169 -0
- src/services/rag/components/routing.py +286 -0
- src/services/rag/factory.py +234 -0
- src/services/rag/pipeline.py +215 -0
- src/services/rag/pipelines/__init__.py +32 -0
- src/services/rag/pipelines/academic.py +44 -0
- src/services/rag/pipelines/lightrag.py +43 -0
- src/services/rag/pipelines/llamaindex.py +313 -0
- src/services/rag/pipelines/raganything.py +384 -0
- src/services/rag/service.py +244 -0
- src/services/rag/types.py +73 -0
- src/services/search/__init__.py +284 -0
- src/services/search/base.py +87 -0
- src/services/search/consolidation.py +398 -0
- src/services/search/providers/__init__.py +128 -0
- src/services/search/providers/baidu.py +188 -0
- src/services/search/providers/exa.py +194 -0
- src/services/search/providers/jina.py +161 -0
- src/services/search/providers/perplexity.py +153 -0
- src/services/search/providers/serper.py +209 -0
- src/services/search/providers/tavily.py +161 -0
- src/services/search/types.py +114 -0
- src/services/setup/__init__.py +34 -0
- src/services/setup/init.py +285 -0
- src/services/tts/__init__.py +16 -0
- src/services/tts/config.py +99 -0
- src/tools/__init__.py +91 -0
- src/tools/code_executor.py +536 -0
- src/tools/paper_search_tool.py +171 -0
- src/tools/query_item_tool.py +310 -0
- src/tools/question/__init__.py +15 -0
- src/tools/question/exam_mimic.py +616 -0
- src/tools/question/pdf_parser.py +211 -0
- src/tools/question/question_extractor.py +397 -0
- src/tools/rag_tool.py +173 -0
- src/tools/tex_chunker.py +339 -0
- src/tools/tex_downloader.py +253 -0
- src/tools/web_search.py +71 -0
- src/utils/config_manager.py +206 -0
- src/utils/document_validator.py +168 -0
- src/utils/error_rate_tracker.py +111 -0
- src/utils/error_utils.py +82 -0
- src/utils/json_parser.py +110 -0
- src/utils/network/circuit_breaker.py +79 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
ManagerAgent - Manager (Refactored: directly plans step-level solution steps)
|
|
5
|
+
Based on user question and knowledge chain, plans solution steps
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
# Add project root to path
|
|
13
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
14
|
+
sys.path.insert(0, str(project_root))
|
|
15
|
+
|
|
16
|
+
from src.agents.base_agent import BaseAgent
|
|
17
|
+
|
|
18
|
+
from ..memory import InvestigateMemory, SolveChainStep, SolveMemory
|
|
19
|
+
from ..utils.json_utils import extract_json_from_text
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ManagerAgent(BaseAgent):
|
|
23
|
+
"""Manager Agent - Plans solution steps"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: dict[str, Any],
|
|
28
|
+
api_key: str,
|
|
29
|
+
base_url: str,
|
|
30
|
+
api_version: str | None = None,
|
|
31
|
+
token_tracker=None,
|
|
32
|
+
):
|
|
33
|
+
language = config.get("system", {}).get("language", "zh")
|
|
34
|
+
super().__init__(
|
|
35
|
+
module_name="solve",
|
|
36
|
+
agent_name="manager_agent",
|
|
37
|
+
api_key=api_key,
|
|
38
|
+
base_url=base_url,
|
|
39
|
+
api_version=api_version,
|
|
40
|
+
language=language,
|
|
41
|
+
config=config,
|
|
42
|
+
token_tracker=token_tracker,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def process(
|
|
46
|
+
self,
|
|
47
|
+
question: str,
|
|
48
|
+
investigate_memory: InvestigateMemory,
|
|
49
|
+
solve_memory: SolveMemory,
|
|
50
|
+
verbose: bool = True,
|
|
51
|
+
) -> dict[str, Any]:
|
|
52
|
+
"""
|
|
53
|
+
Process management workflow - plan solution steps
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
question: User question
|
|
57
|
+
investigate_memory: Investigation memory (contains knowledge chain)
|
|
58
|
+
solve_memory: Solve memory
|
|
59
|
+
verbose: Whether to print detailed information
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
dict: Management result
|
|
63
|
+
"""
|
|
64
|
+
stage_label = "Plan"
|
|
65
|
+
self.logger.log_stage_progress(
|
|
66
|
+
stage_label, "start", f"question={question[:60]}{'...' if len(question) > 60 else ''}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# 1. Check if steps already exist
|
|
70
|
+
if solve_memory.solve_chains:
|
|
71
|
+
steps_count = len(solve_memory.solve_chains)
|
|
72
|
+
self.logger.log_stage_progress(stage_label, "skip", f"Already has {steps_count} steps")
|
|
73
|
+
return {
|
|
74
|
+
"has_steps": True,
|
|
75
|
+
"steps_count": steps_count,
|
|
76
|
+
"num_steps": steps_count, # Maintain compatibility
|
|
77
|
+
"message": "Steps already exist, skipping planning",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# 2. Build context
|
|
81
|
+
context = self._build_context(question=question, investigate_memory=investigate_memory)
|
|
82
|
+
|
|
83
|
+
# 3. Build Prompt
|
|
84
|
+
system_prompt = self._build_system_prompt()
|
|
85
|
+
user_prompt = self._build_user_prompt(context)
|
|
86
|
+
|
|
87
|
+
# 4. Call LLM (requires JSON format output)
|
|
88
|
+
response = await self.call_llm(
|
|
89
|
+
user_prompt=user_prompt,
|
|
90
|
+
system_prompt=system_prompt,
|
|
91
|
+
verbose=verbose,
|
|
92
|
+
stage=stage_label,
|
|
93
|
+
response_format={"type": "json_object"}, # Force JSON
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# 5. Parse output and create StepItem
|
|
97
|
+
steps = self._parse_response(response, investigate_memory)
|
|
98
|
+
|
|
99
|
+
# 6. Add steps to solve_memory
|
|
100
|
+
solve_memory.create_chains(steps)
|
|
101
|
+
solve_memory.save()
|
|
102
|
+
|
|
103
|
+
steps_count = len(steps)
|
|
104
|
+
self.logger.log_stage_progress(stage_label, "complete", f"Generated {steps_count} steps")
|
|
105
|
+
return {
|
|
106
|
+
"has_steps": True,
|
|
107
|
+
"steps_count": steps_count,
|
|
108
|
+
"num_steps": steps_count, # Maintain compatibility
|
|
109
|
+
"message": f"Generated {steps_count} steps",
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
def _build_context(
|
|
113
|
+
self, question: str, investigate_memory: InvestigateMemory
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Build context"""
|
|
116
|
+
# Get knowledge chain information (cite_id + summary)
|
|
117
|
+
knowledge_info = []
|
|
118
|
+
for knowledge in investigate_memory.knowledge_chain:
|
|
119
|
+
if knowledge.summary: # Only use knowledge with summary
|
|
120
|
+
knowledge_info.append(
|
|
121
|
+
{
|
|
122
|
+
"cite_id": knowledge.cite_id,
|
|
123
|
+
"tool_type": knowledge.tool_type,
|
|
124
|
+
"query": knowledge.query,
|
|
125
|
+
"summary": knowledge.summary,
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
knowledge_text = ""
|
|
130
|
+
for info in knowledge_info:
|
|
131
|
+
knowledge_text += f"\n{info['cite_id']} [{info['tool_type']}]\n"
|
|
132
|
+
knowledge_text += f" Query: {info['query']}\n"
|
|
133
|
+
knowledge_text += f" Summary: {info['summary']}\n"
|
|
134
|
+
|
|
135
|
+
remaining_questions = []
|
|
136
|
+
if investigate_memory and getattr(investigate_memory, "reflections", None):
|
|
137
|
+
remaining_questions = investigate_memory.reflections.remaining_questions or []
|
|
138
|
+
|
|
139
|
+
reflections_summary = (
|
|
140
|
+
"\n".join(f"- {q}" for q in remaining_questions)
|
|
141
|
+
if remaining_questions
|
|
142
|
+
else "(No remaining questions)"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
knowledge_summary_text = knowledge_text if knowledge_text else "(No research information)"
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"question": question,
|
|
149
|
+
"knowledge_info": knowledge_info,
|
|
150
|
+
"knowledge_text": knowledge_summary_text,
|
|
151
|
+
"knowledge_chain_summary": knowledge_summary_text,
|
|
152
|
+
"reflections_summary": reflections_summary,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
def _build_system_prompt(self) -> str:
|
|
156
|
+
"""Build system prompt"""
|
|
157
|
+
prompt = self.get_prompt("system") if self.has_prompts() else None
|
|
158
|
+
if not prompt:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
"ManagerAgent missing system prompt, please configure system section in prompts/zh/solve_loop/manager_agent.yaml."
|
|
161
|
+
)
|
|
162
|
+
return prompt
|
|
163
|
+
|
|
164
|
+
def _build_user_prompt(self, context: dict[str, Any]) -> str:
|
|
165
|
+
"""Build user prompt"""
|
|
166
|
+
template = self.get_prompt("user_template") if self.has_prompts() else None
|
|
167
|
+
if not template:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"ManagerAgent missing user prompt template, please configure user_template in prompts/zh/solve_loop/manager_agent.yaml."
|
|
170
|
+
)
|
|
171
|
+
return template.format(**context)
|
|
172
|
+
|
|
173
|
+
def _parse_response(
|
|
174
|
+
self, response: str, investigate_memory: InvestigateMemory
|
|
175
|
+
) -> list[SolveChainStep]:
|
|
176
|
+
"""Parse LLM output (JSON format), create solve-chain steps"""
|
|
177
|
+
steps: list[SolveChainStep] = []
|
|
178
|
+
knowledge_ids = {k.cite_id for k in investigate_memory.knowledge_chain}
|
|
179
|
+
|
|
180
|
+
# Use json_utils to extract JSON
|
|
181
|
+
parsed_data = extract_json_from_text(response)
|
|
182
|
+
|
|
183
|
+
if not parsed_data or not isinstance(parsed_data, dict):
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"Failed to parse valid JSON object from LLM output. Original output: {response[:200]}..."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
steps_data = parsed_data.get("steps", [])
|
|
189
|
+
if not isinstance(steps_data, list):
|
|
190
|
+
raise ValueError(f"'steps' field in JSON is not an array. Parsed result: {parsed_data}")
|
|
191
|
+
|
|
192
|
+
if not steps_data:
|
|
193
|
+
raise ValueError("'steps' array in JSON is empty, please check LLM output")
|
|
194
|
+
|
|
195
|
+
# Parse each step
|
|
196
|
+
for idx, step_data in enumerate(steps_data, 1):
|
|
197
|
+
if not isinstance(step_data, dict):
|
|
198
|
+
self.logger.warning(
|
|
199
|
+
f"[ManagerAgent] Skipping invalid step data (index {idx}): {step_data}"
|
|
200
|
+
)
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Get step_id
|
|
204
|
+
step_id = step_data.get("step_id", "").strip()
|
|
205
|
+
if not step_id:
|
|
206
|
+
step_id = f"S{idx}"
|
|
207
|
+
elif not step_id.upper().startswith("S"):
|
|
208
|
+
step_id = f"S{step_id}"
|
|
209
|
+
|
|
210
|
+
# Get role and target
|
|
211
|
+
role = step_data.get("role", "").strip()
|
|
212
|
+
target = step_data.get("target", "").strip()
|
|
213
|
+
|
|
214
|
+
# If target already contains role, use directly; otherwise combine
|
|
215
|
+
if target:
|
|
216
|
+
if ":" in target or ":" in target:
|
|
217
|
+
step_target = target
|
|
218
|
+
elif role:
|
|
219
|
+
step_target = f"{role}:{target}"
|
|
220
|
+
else:
|
|
221
|
+
step_target = target
|
|
222
|
+
else:
|
|
223
|
+
raise ValueError(f"Step {step_id} missing 'target' field")
|
|
224
|
+
|
|
225
|
+
# Get cite_ids
|
|
226
|
+
cite_ids_raw = step_data.get("cite_ids", [])
|
|
227
|
+
if not isinstance(cite_ids_raw, list):
|
|
228
|
+
# Compatible with string format
|
|
229
|
+
if isinstance(cite_ids_raw, str):
|
|
230
|
+
cite_ids_raw = [cite_ids_raw] if cite_ids_raw and cite_ids_raw != "none" else []
|
|
231
|
+
else:
|
|
232
|
+
cite_ids_raw = []
|
|
233
|
+
|
|
234
|
+
# Clean and normalize cite_ids
|
|
235
|
+
filtered_cites = []
|
|
236
|
+
for cite in cite_ids_raw:
|
|
237
|
+
if not cite or cite == "none":
|
|
238
|
+
continue
|
|
239
|
+
# Ensure format is [xxx]
|
|
240
|
+
cleaned = str(cite).strip()
|
|
241
|
+
if not cleaned.startswith("["):
|
|
242
|
+
cleaned = f"[{cleaned.strip('[] ')}]"
|
|
243
|
+
|
|
244
|
+
# Filter invalid cite
|
|
245
|
+
if cleaned in knowledge_ids:
|
|
246
|
+
filtered_cites.append(cleaned)
|
|
247
|
+
else:
|
|
248
|
+
self.logger.warning(
|
|
249
|
+
f"[ManagerAgent] Skipping unknown cite_id {cleaned} (not in knowledge chain)"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Create step
|
|
253
|
+
steps.append(
|
|
254
|
+
SolveChainStep(
|
|
255
|
+
step_id=step_id,
|
|
256
|
+
step_target=step_target,
|
|
257
|
+
available_cite=list(dict.fromkeys(filtered_cites)), # Remove duplicates
|
|
258
|
+
status="undone",
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if not steps:
|
|
263
|
+
raise ValueError("Failed to parse any valid steps, please check LLM output format")
|
|
264
|
+
|
|
265
|
+
logger = getattr(self, "logger", None)
|
|
266
|
+
if logger is not None:
|
|
267
|
+
logger.info(f"[ManagerAgent._parse_response] Parsed {len(steps)} solve-chain steps")
|
|
268
|
+
for step in steps:
|
|
269
|
+
logger.info(f" - {step.step_id}: {step.step_target}")
|
|
270
|
+
logger.info(
|
|
271
|
+
f" Available citations: {', '.join(step.available_cite) or '(none)'}"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return steps
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
PrecisionAnswerAgent - Precision answer generator (two-stage)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
12
|
+
if str(project_root) not in sys.path:
|
|
13
|
+
sys.path.insert(0, str(project_root))
|
|
14
|
+
|
|
15
|
+
from src.agents.base_agent import BaseAgent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PrecisionAnswerAgent(BaseAgent):
|
|
19
|
+
"""Staged precision answer"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
config: Dict[str, Any],
|
|
24
|
+
api_key: str,
|
|
25
|
+
base_url: str,
|
|
26
|
+
api_version: str | None = None,
|
|
27
|
+
token_tracker=None,
|
|
28
|
+
):
|
|
29
|
+
language = config.get("system", {}).get("language", "zh")
|
|
30
|
+
super().__init__(
|
|
31
|
+
module_name="solve",
|
|
32
|
+
agent_name="precision_answer_agent",
|
|
33
|
+
api_key=api_key,
|
|
34
|
+
base_url=base_url,
|
|
35
|
+
api_version=api_version,
|
|
36
|
+
language=language,
|
|
37
|
+
config=config,
|
|
38
|
+
token_tracker=token_tracker,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def process(
|
|
42
|
+
self, question: str, detailed_answer: str, verbose: bool = True
|
|
43
|
+
) -> Dict[str, Any]:
|
|
44
|
+
decision = await self._should_generate(question, verbose)
|
|
45
|
+
if not decision["needs_precision"]:
|
|
46
|
+
return {
|
|
47
|
+
"needs_precision": False,
|
|
48
|
+
"precision_answer": "",
|
|
49
|
+
"final_answer": detailed_answer,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
precision_answer = await self._generate_precision_answer(
|
|
53
|
+
question=question, detailed_answer=detailed_answer, verbose=verbose
|
|
54
|
+
)
|
|
55
|
+
return {
|
|
56
|
+
"needs_precision": True,
|
|
57
|
+
"precision_answer": precision_answer,
|
|
58
|
+
"final_answer": detailed_answer,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async def _should_generate(self, question: str, verbose: bool) -> Dict[str, Any]:
|
|
62
|
+
system_prompt = self.get_prompt("decision_system") if self.has_prompts() else None
|
|
63
|
+
if not system_prompt:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"PrecisionAnswerAgent missing decision_system prompt, please configure decision_system in prompts/{lang}/solve_loop/precision_answer_agent.yaml"
|
|
66
|
+
)
|
|
67
|
+
template = self.get_prompt("decision_user_template") if self.has_prompts() else None
|
|
68
|
+
if not template:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
"PrecisionAnswerAgent missing decision_user_template, please configure decision_user_template in prompts/{lang}/solve_loop/precision_answer_agent.yaml"
|
|
71
|
+
)
|
|
72
|
+
user_prompt = template.format(question=question)
|
|
73
|
+
response = await self.call_llm(
|
|
74
|
+
user_prompt=user_prompt, system_prompt=system_prompt, verbose=verbose
|
|
75
|
+
)
|
|
76
|
+
needs_precision = response.strip().upper().startswith("Y")
|
|
77
|
+
return {"needs_precision": needs_precision, "raw_decision": response.strip()}
|
|
78
|
+
|
|
79
|
+
async def _generate_precision_answer(
|
|
80
|
+
self, question: str, detailed_answer: str, verbose: bool
|
|
81
|
+
) -> str:
|
|
82
|
+
system_prompt = self.get_prompt("precision_system") if self.has_prompts() else None
|
|
83
|
+
if not system_prompt:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"PrecisionAnswerAgent missing precision_system prompt, please configure precision_system in prompts/{lang}/solve_loop/precision_answer_agent.yaml"
|
|
86
|
+
)
|
|
87
|
+
template = self.get_prompt("precision_user_template") if self.has_prompts() else None
|
|
88
|
+
if not template:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"PrecisionAnswerAgent missing precision_user_template, please configure precision_user_template in prompts/{lang}/solve_loop/precision_answer_agent.yaml"
|
|
91
|
+
)
|
|
92
|
+
user_prompt = template.format(question=question, detailed_answer=detailed_answer)
|
|
93
|
+
response = await self.call_llm(
|
|
94
|
+
user_prompt=user_prompt, system_prompt=system_prompt, verbose=verbose
|
|
95
|
+
)
|
|
96
|
+
return response.strip()
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
ResponseAgent - Step response generator
|
|
5
|
+
Based on materials in solve-chain, generates formal response for current step
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
14
|
+
if str(project_root) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(project_root))
|
|
16
|
+
|
|
17
|
+
from src.agents.base_agent import BaseAgent
|
|
18
|
+
|
|
19
|
+
from ..memory import CitationMemory, InvestigateMemory, SolveChainStep, SolveMemory
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ResponseAgent(BaseAgent):
|
|
23
|
+
"""Response generator Agent"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: dict[str, Any],
|
|
28
|
+
api_key: str,
|
|
29
|
+
base_url: str,
|
|
30
|
+
api_version: str | None = None,
|
|
31
|
+
token_tracker=None,
|
|
32
|
+
):
|
|
33
|
+
language = config.get("system", {}).get("language", "zh")
|
|
34
|
+
super().__init__(
|
|
35
|
+
module_name="solve",
|
|
36
|
+
agent_name="response_agent",
|
|
37
|
+
api_key=api_key,
|
|
38
|
+
base_url=base_url,
|
|
39
|
+
api_version=api_version,
|
|
40
|
+
language=language,
|
|
41
|
+
config=config,
|
|
42
|
+
token_tracker=token_tracker,
|
|
43
|
+
)
|
|
44
|
+
# Store citation configuration
|
|
45
|
+
self.enable_citations = config.get("system", {}).get("enable_citations", True)
|
|
46
|
+
|
|
47
|
+
async def process(
|
|
48
|
+
self,
|
|
49
|
+
question: str,
|
|
50
|
+
step: SolveChainStep,
|
|
51
|
+
solve_memory: SolveMemory,
|
|
52
|
+
investigate_memory: InvestigateMemory,
|
|
53
|
+
citation_memory: CitationMemory,
|
|
54
|
+
output_dir: str | None = None,
|
|
55
|
+
verbose: bool = True,
|
|
56
|
+
accumulated_response: str = "",
|
|
57
|
+
) -> dict[str, Any]:
|
|
58
|
+
if not step:
|
|
59
|
+
return {"step_response": "(No pending step)"}
|
|
60
|
+
|
|
61
|
+
context = self._build_context(
|
|
62
|
+
question=question,
|
|
63
|
+
step=step,
|
|
64
|
+
solve_memory=solve_memory,
|
|
65
|
+
investigate_memory=investigate_memory,
|
|
66
|
+
citation_memory=citation_memory,
|
|
67
|
+
output_dir=output_dir,
|
|
68
|
+
accumulated_response=accumulated_response,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
system_prompt = self._build_system_prompt(context["image_materials"])
|
|
72
|
+
user_prompt = self._build_user_prompt(context)
|
|
73
|
+
|
|
74
|
+
response = await self.call_llm(
|
|
75
|
+
user_prompt=user_prompt, system_prompt=system_prompt, verbose=verbose
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Directly use LLM's raw output as step_response, no parsing
|
|
79
|
+
step_response = response.strip() if response else ""
|
|
80
|
+
used_citations = self._extract_used_citations(step_response, step)
|
|
81
|
+
solve_memory.submit_step_response(
|
|
82
|
+
step_id=step.step_id, response=step_response, used_citations=used_citations
|
|
83
|
+
)
|
|
84
|
+
solve_memory.save()
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
"step_id": step.step_id,
|
|
88
|
+
"step_response": step_response,
|
|
89
|
+
"used_citations": used_citations,
|
|
90
|
+
"raw_response": response,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------ #
|
|
94
|
+
# Prompt Building
|
|
95
|
+
# ------------------------------------------------------------------ #
|
|
96
|
+
def _build_context(
|
|
97
|
+
self,
|
|
98
|
+
question: str,
|
|
99
|
+
step: SolveChainStep,
|
|
100
|
+
solve_memory: SolveMemory,
|
|
101
|
+
investigate_memory: InvestigateMemory,
|
|
102
|
+
citation_memory: CitationMemory,
|
|
103
|
+
output_dir: str | None,
|
|
104
|
+
accumulated_response: str = "",
|
|
105
|
+
) -> dict[str, Any]:
|
|
106
|
+
available_cite_details = self._format_available_cite(step, investigate_memory)
|
|
107
|
+
tool_materials, image_materials = self._format_tool_materials(step, output_dir)
|
|
108
|
+
citation_details = self._format_citation_details(step, citation_memory)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"question": question,
|
|
112
|
+
"step_id": step.step_id,
|
|
113
|
+
"step_target": step.step_target,
|
|
114
|
+
"available_cite_details": available_cite_details,
|
|
115
|
+
"tool_materials": tool_materials,
|
|
116
|
+
"citation_details": citation_details,
|
|
117
|
+
"image_materials": image_materials,
|
|
118
|
+
"previous_context": accumulated_response
|
|
119
|
+
or "(No previous content, this is the first step)",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def _build_system_prompt(self, image_materials: list[str]) -> str:
|
|
123
|
+
base_prompt = self.get_prompt("system") if self.has_prompts() else None
|
|
124
|
+
if not base_prompt:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"ResponseAgent missing system prompt, please configure in prompts/zh/solve_loop/response_agent.yaml."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Add citation disable instruction if citations are disabled
|
|
130
|
+
citation_instruction = ""
|
|
131
|
+
if not self.enable_citations:
|
|
132
|
+
citation_instruction_yaml = self.get_prompt("citation_instruction_disabled")
|
|
133
|
+
if citation_instruction_yaml:
|
|
134
|
+
citation_instruction = citation_instruction_yaml
|
|
135
|
+
else:
|
|
136
|
+
citation_instruction = "\n\n**Important: Citation Feature Disabled**\n"
|
|
137
|
+
|
|
138
|
+
if image_materials:
|
|
139
|
+
image_list = "\n".join([f" - {img}" for img in image_materials])
|
|
140
|
+
image_instruction_template = self.get_prompt("image_instruction")
|
|
141
|
+
if image_instruction_template:
|
|
142
|
+
image_instruction = image_instruction_template.format(image_list=image_list)
|
|
143
|
+
else:
|
|
144
|
+
image_instruction = f"\n\n**Image files to insert**:\n{image_list}\n"
|
|
145
|
+
return base_prompt + citation_instruction + image_instruction
|
|
146
|
+
|
|
147
|
+
return base_prompt + citation_instruction
|
|
148
|
+
|
|
149
|
+
def _build_user_prompt(self, context: dict[str, Any]) -> str:
|
|
150
|
+
template = self.get_prompt("user_template") if self.has_prompts() else None
|
|
151
|
+
if not template:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"ResponseAgent missing user_template, please configure user_template in prompts/{lang}/solve_loop/response_agent.yaml"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Format image_materials as clear text list
|
|
157
|
+
image_materials = context.get("image_materials", [])
|
|
158
|
+
if image_materials:
|
|
159
|
+
image_text = "\n".join([f"- {img}" for img in image_materials])
|
|
160
|
+
else:
|
|
161
|
+
image_text = "(No image files)"
|
|
162
|
+
|
|
163
|
+
# Create new context, format image_materials as text
|
|
164
|
+
formatted_context = context.copy()
|
|
165
|
+
formatted_context["image_materials"] = image_text
|
|
166
|
+
|
|
167
|
+
return template.format(**formatted_context)
|
|
168
|
+
|
|
169
|
+
# ------------------------------------------------------------------ #
|
|
170
|
+
# Material Organization
|
|
171
|
+
# ------------------------------------------------------------------ #
|
|
172
|
+
def _format_available_cite(
|
|
173
|
+
self, step: SolveChainStep, investigate_memory: InvestigateMemory
|
|
174
|
+
) -> str:
|
|
175
|
+
if not step.available_cite:
|
|
176
|
+
return "(No available knowledge chain)"
|
|
177
|
+
lines: list[str] = []
|
|
178
|
+
for cite in step.available_cite:
|
|
179
|
+
knowledge = next(
|
|
180
|
+
(k for k in investigate_memory.knowledge_chain if k.cite_id == cite), None
|
|
181
|
+
)
|
|
182
|
+
if not knowledge:
|
|
183
|
+
continue
|
|
184
|
+
summary = knowledge.summary or knowledge.raw_result[:300]
|
|
185
|
+
lines.append(
|
|
186
|
+
f"{cite} [{knowledge.tool_type}]\n"
|
|
187
|
+
f" Query: {knowledge.query}\n"
|
|
188
|
+
f" Summary: {summary}\n"
|
|
189
|
+
f" Raw: {knowledge.raw_result[:300]}..."
|
|
190
|
+
)
|
|
191
|
+
return "\n".join(lines) if lines else "(No matching knowledge)"
|
|
192
|
+
|
|
193
|
+
def _format_tool_materials(
|
|
194
|
+
self, step: SolveChainStep, output_dir: str | None
|
|
195
|
+
) -> tuple[str, list[str]]:
|
|
196
|
+
if not step.tool_calls:
|
|
197
|
+
return "(No tool calls yet)", []
|
|
198
|
+
|
|
199
|
+
lines: list[str] = []
|
|
200
|
+
images: list[str] = []
|
|
201
|
+
seen_images: set[str] = set()
|
|
202
|
+
|
|
203
|
+
def _append_image(path_str: str):
|
|
204
|
+
normalized = str(path_str).replace("\\", "/")
|
|
205
|
+
if normalized and normalized not in seen_images:
|
|
206
|
+
images.append(normalized)
|
|
207
|
+
seen_images.add(normalized)
|
|
208
|
+
|
|
209
|
+
for call in step.tool_calls:
|
|
210
|
+
summary = call.summary or "(Summary pending)"
|
|
211
|
+
raw_preview = (call.raw_answer or "")[:500]
|
|
212
|
+
lines.append(
|
|
213
|
+
f"{call.tool_type} | cite_id={call.cite_id} | Status={call.status}\n"
|
|
214
|
+
f"Query: {call.query}\n"
|
|
215
|
+
f"Summary: {summary}\n"
|
|
216
|
+
f"Raw excerpt: {raw_preview}"
|
|
217
|
+
)
|
|
218
|
+
# Priority: use recorded relative paths, then absolute paths, finally fall back to original artifacts list
|
|
219
|
+
artifact_rel_paths = call.metadata.get("artifact_rel_paths") if call.metadata else None
|
|
220
|
+
artifact_paths = call.metadata.get("artifact_paths") if call.metadata else None
|
|
221
|
+
artifacts = call.metadata.get("artifacts") if call.metadata else None
|
|
222
|
+
|
|
223
|
+
if artifact_rel_paths:
|
|
224
|
+
for rel_path in artifact_rel_paths:
|
|
225
|
+
_append_image(rel_path)
|
|
226
|
+
|
|
227
|
+
if artifact_paths:
|
|
228
|
+
# Use absolute paths, but convert to relative paths relative to output_dir for display
|
|
229
|
+
for abs_path in artifact_paths:
|
|
230
|
+
path = Path(abs_path)
|
|
231
|
+
if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp"}:
|
|
232
|
+
# If output_dir exists, calculate relative path; otherwise use absolute path
|
|
233
|
+
if output_dir:
|
|
234
|
+
try:
|
|
235
|
+
rel_path = path.relative_to(Path(output_dir))
|
|
236
|
+
_append_image(str(rel_path))
|
|
237
|
+
except ValueError:
|
|
238
|
+
# If cannot calculate relative path, use filename
|
|
239
|
+
_append_image(path.name)
|
|
240
|
+
else:
|
|
241
|
+
_append_image(path.name)
|
|
242
|
+
elif artifacts:
|
|
243
|
+
# Fall back to using artifacts list
|
|
244
|
+
for artifact in artifacts:
|
|
245
|
+
path = Path(artifact)
|
|
246
|
+
if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp"}:
|
|
247
|
+
# Build relative path to artifacts directory
|
|
248
|
+
rel_path = Path("artifacts") / path.name
|
|
249
|
+
_append_image(str(rel_path))
|
|
250
|
+
return "\n\n".join(lines), images
|
|
251
|
+
|
|
252
|
+
def _format_citation_details(
|
|
253
|
+
self, step: SolveChainStep, citation_memory: CitationMemory
|
|
254
|
+
) -> str:
|
|
255
|
+
# If citations are disabled, return empty string
|
|
256
|
+
if not self.enable_citations:
|
|
257
|
+
return "(Citations disabled)"
|
|
258
|
+
|
|
259
|
+
cite_ids = list(
|
|
260
|
+
dict.fromkeys(
|
|
261
|
+
step.available_cite + [tc.cite_id for tc in step.tool_calls if tc.cite_id]
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
if not cite_ids:
|
|
265
|
+
return "(No citations)"
|
|
266
|
+
lines: list[str] = []
|
|
267
|
+
for cite_id in cite_ids:
|
|
268
|
+
citation = citation_memory.get_citation(cite_id)
|
|
269
|
+
if not citation:
|
|
270
|
+
continue
|
|
271
|
+
summary = citation.content or citation.raw_result[:200]
|
|
272
|
+
lines.append(f"- {cite_id} [{citation.tool_type}] Query: {citation.query}")
|
|
273
|
+
if summary:
|
|
274
|
+
lines.append(f" Summary: {summary[:300]}")
|
|
275
|
+
return "\n".join(lines) if lines else "(Citation information missing)"
|
|
276
|
+
|
|
277
|
+
# ------------------------------------------------------------------ #
|
|
278
|
+
# Citation Extraction
|
|
279
|
+
# ------------------------------------------------------------------ #
|
|
280
|
+
def _extract_used_citations(self, content: str, step: SolveChainStep) -> list[str]:
|
|
281
|
+
# If citations are disabled, return empty list
|
|
282
|
+
if not self.enable_citations:
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
if not content:
|
|
286
|
+
return []
|
|
287
|
+
# Allow standard English brackets [cite], also tolerate some error formats (like Chinese full-width brackets【cite】), uniformly normalize to [cite]
|
|
288
|
+
pattern = re.compile(r"\[([^\]\[]+)\](?!\()|【([^】\[]+)】")
|
|
289
|
+
matches = pattern.findall(content)
|
|
290
|
+
normalized: list[str] = []
|
|
291
|
+
for match in matches:
|
|
292
|
+
candidate = match[0] or match[1]
|
|
293
|
+
if not candidate:
|
|
294
|
+
continue
|
|
295
|
+
normalized.append(f"[{candidate.strip()}]")
|
|
296
|
+
allowed = set(step.available_cite + [tc.cite_id for tc in step.tool_calls if tc.cite_id])
|
|
297
|
+
ordered: list[str] = []
|
|
298
|
+
for cite in normalized:
|
|
299
|
+
if cite in allowed and cite not in ordered:
|
|
300
|
+
ordered.append(cite)
|
|
301
|
+
return ordered
|