forked from assafelovic/gptr-mcp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
255 lines (194 loc) · 7.55 KB
/
Copy pathutils.py
File metadata and controls
255 lines (194 loc) · 7.55 KB
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""
GPT Researcher MCP Server Utilities
This module provides utility functions and helpers for the GPT Researcher MCP Server.
"""
import re
import html
import sys
from typing import Dict, List, Optional, Tuple, Any
from loguru import logger
try:
from markdownify import markdownify as md
HAS_MARKDOWNIFY = True
except ImportError:
HAS_MARKDOWNIFY = False
# Configure logging for console only (no file logging)
logger.configure(handlers=[{"sink": sys.stderr, "level": "INFO"}])
# Research store to track ongoing research topics and contexts
research_store = {}
# HTML/Markdown Utilities
def html_to_markdown(text: str) -> str:
"""
Convert HTML to Markdown, preserving structure (tables, headers, lists, etc.)
Falls back to simple tag stripping if markdownify is not available.
Args:
text: Text that may contain HTML
Returns:
Clean Markdown text
"""
if not text:
return text
# Decode HTML entities first
text = html.unescape(text)
# Check if text contains HTML tags
if '<' not in text or '>' not in text:
return text
# Use markdownify if available (preserves structure)
if HAS_MARKDOWNIFY:
try:
result = md(text, heading_style="atx", strip=['script', 'style'])
# Clean up excessive newlines
result = re.sub(r'\n{3,}', '\n\n', result)
return result.strip()
except Exception:
pass # Fall through to basic stripping
# Fallback: strip tags (loses structure but removes HTML)
text = re.sub(r'<[^>]+>', '', text)
text = re.sub(r'\s+', ' ', text)
return text.strip()
def strip_html(text: str) -> str:
"""
Strip HTML tags and decode HTML entities from text.
Deprecated: Use html_to_markdown() for better results.
Args:
text: Text that may contain HTML tags or entities
Returns:
Clean text with HTML removed
"""
if not text:
return text
# Decode HTML entities (e.g., & -> &,   -> space, \u00b7 -> ·)
text = html.unescape(text)
# Remove HTML tags
text = re.sub(r'<[^>]+>', '', text)
# Remove excess whitespace
text = re.sub(r'\s+', ' ', text)
return text.strip()
def clean_search_results(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Convert HTML to Markdown in search results (typically from DuckDuckGo body field).
Args:
results: List of search result dictionaries
Returns:
Cleaned search results with Markdown
"""
cleaned = []
for result in results:
cleaned_result = {}
for key, value in result.items():
if isinstance(value, str):
cleaned_result[key] = html_to_markdown(value)
else:
cleaned_result[key] = value
cleaned.append(cleaned_result)
return cleaned
def clean_context(context: Any) -> str:
"""
Convert HTML to Markdown from research context.
Args:
context: Research context (string or list of strings)
Returns:
Clean Markdown text
"""
if isinstance(context, list):
# Context is a list of strings
cleaned_parts = [html_to_markdown(part) if isinstance(part, str) else str(part) for part in context]
return '\n\n'.join(cleaned_parts)
elif isinstance(context, str):
return html_to_markdown(context)
else:
return str(context)
# API Response Utilities
def create_error_response(message: str) -> Dict[str, Any]:
"""Create a standardized error response"""
return {"status": "error", "message": message}
def create_success_response(data: Dict[str, Any]) -> Dict[str, Any]:
"""Create a standardized success response"""
return {"status": "success", **data}
def handle_exception(e: Exception, operation: str) -> Dict[str, Any]:
"""Handle exceptions in a consistent way"""
error_message = str(e)
logger.error(f"{operation} failed: {error_message}")
return create_error_response(error_message)
def get_researcher_by_id(researchers_dict: Dict, research_id: str) -> Tuple[bool, Any, Dict[str, Any]]:
"""
Helper function to retrieve a researcher by ID.
Args:
researchers_dict: Dictionary of research objects
research_id: The ID of the research session
Returns:
Tuple containing (success, researcher_object, error_response)
"""
if not researchers_dict or research_id not in researchers_dict:
return False, None, create_error_response("Research ID not found. Please conduct research first.")
return True, researchers_dict[research_id], {}
def format_sources_for_response(sources: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Format source information for API responses.
Args:
sources: List of source dictionaries
Returns:
Formatted source list for API responses
"""
return [
{
"title": source.get("title", "Unknown"),
"url": source.get("url", ""),
"content_length": len(source.get("content", ""))
}
for source in sources
]
def format_context_with_sources(topic: str, context: str, sources: List[Dict[str, Any]]) -> str:
"""
Format research context with sources for display.
Args:
topic: Research topic
context: Research context
sources: List of sources
Returns:
Formatted context string with sources
"""
formatted_context = f"## Research: {topic}\n\n{context}\n\n"
formatted_context += "## Sources:\n"
for i, source in enumerate(sources):
formatted_context += f"{i+1}. {source.get('title', 'Unknown')}: {source.get('url', '')}\n"
return formatted_context
def store_research_results(topic: str, context: str, sources: List[Dict[str, Any]],
source_urls: List[str], formatted_context: Optional[str] = None):
"""
Store research results in the research store.
Args:
topic: Research topic
context: Research context
sources: List of sources
source_urls: List of source URLs
formatted_context: Optional pre-formatted context
"""
research_store[topic] = {
"context": formatted_context or context,
"sources": sources,
"source_urls": source_urls
}
def create_research_prompt(topic: str, goal: str, report_format: str = "research_report") -> str:
"""
Create a research query prompt for GPT Researcher.
Args:
topic: The topic to research
goal: The goal or specific question to answer
report_format: The format of the report to generate
Returns:
A formatted prompt for research
"""
return f"""
Please research the following topic: {topic}
Goal: {goal}
You have two methods to access web-sourced information:
1. Use the "research://{topic}" resource to directly access context about this topic if it exists
or if you want to get straight to the information without tracking a research ID.
2. Use the deep_research tool to perform new research and get a research_id for later use.
This tool also returns the context directly in its response, which you can use immediately.
After getting context, you can:
- Use it directly in your response
- Use the write_report tool with a custom prompt to generate a structured {report_format}
You can also use get_research_sources to view additional details about the information sources.
"""