ngpt 3.2.0__py3-none-any.whl → 3.4.0__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.
- ngpt/cli/args.py +1 -1
- ngpt/cli/interactive.py +28 -2
- ngpt/cli/modes/chat.py +17 -1
- ngpt/cli/modes/code.py +17 -2
- ngpt/cli/modes/rewrite.py +16 -1
- ngpt/cli/modes/shell.py +19 -3
- ngpt/cli/modes/text.py +17 -1
- ngpt/client.py +0 -12
- ngpt/utils/__init__.py +7 -1
- ngpt/utils/web_search.py +291 -0
- {ngpt-3.2.0.dist-info → ngpt-3.4.0.dist-info}/METADATA +5 -3
- ngpt-3.4.0.dist-info/RECORD +28 -0
- ngpt-3.2.0.dist-info/RECORD +0 -27
- {ngpt-3.2.0.dist-info → ngpt-3.4.0.dist-info}/WHEEL +0 -0
- {ngpt-3.2.0.dist-info → ngpt-3.4.0.dist-info}/entry_points.txt +0 -0
- {ngpt-3.2.0.dist-info → ngpt-3.4.0.dist-info}/licenses/LICENSE +0 -0
ngpt/cli/args.py
CHANGED
@@ -68,7 +68,7 @@ def setup_argument_parser():
|
|
68
68
|
global_group.add_argument('--model',
|
69
69
|
help='Model to use')
|
70
70
|
global_group.add_argument('--web-search', action='store_true',
|
71
|
-
help='Enable web search capability
|
71
|
+
help='Enable web search capability using DuckDuckGo to enhance prompts with relevant information')
|
72
72
|
global_group.add_argument('--temperature', type=float, default=0.7,
|
73
73
|
help='Set temperature (controls randomness, default: 0.7)')
|
74
74
|
global_group.add_argument('--top_p', type=float, default=1.0,
|
ngpt/cli/interactive.py
CHANGED
@@ -6,6 +6,7 @@ import sys
|
|
6
6
|
import time
|
7
7
|
from .formatters import COLORS
|
8
8
|
from .renderers import prettify_markdown, prettify_streaming_markdown
|
9
|
+
from ..utils import enhance_prompt_with_web_search
|
9
10
|
|
10
11
|
# Optional imports for enhanced UI
|
11
12
|
try:
|
@@ -64,6 +65,10 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
64
65
|
if logger:
|
65
66
|
print(f"{COLORS['green']}Logging conversation to: {logger.get_log_path()}{COLORS['reset']}")
|
66
67
|
|
68
|
+
# Display a note about web search if enabled
|
69
|
+
if web_search:
|
70
|
+
print(f"{COLORS['green']}Web search capability is enabled.{COLORS['reset']}")
|
71
|
+
|
67
72
|
# Display a note about markdown rendering only once at the beginning
|
68
73
|
if prettify and not no_stream and not stream_prettify:
|
69
74
|
print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
|
@@ -188,6 +193,28 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
188
193
|
# Log user message if logging is enabled
|
189
194
|
if logger:
|
190
195
|
logger.log("user", user_input)
|
196
|
+
|
197
|
+
# Enhance prompt with web search if enabled
|
198
|
+
enhanced_prompt = user_input
|
199
|
+
if web_search:
|
200
|
+
try:
|
201
|
+
print(f"{COLORS['cyan']}Searching the web...{COLORS['reset']}")
|
202
|
+
enhanced_prompt = enhance_prompt_with_web_search(user_input, logger=logger)
|
203
|
+
print(f"{COLORS['green']}Enhanced input with web search results.{COLORS['reset']}")
|
204
|
+
|
205
|
+
# Update the user message in conversation with enhanced prompt
|
206
|
+
for i in range(len(conversation) - 1, -1, -1):
|
207
|
+
if conversation[i]["role"] == "user" and conversation[i]["content"] == user_input:
|
208
|
+
conversation[i]["content"] = enhanced_prompt
|
209
|
+
break
|
210
|
+
|
211
|
+
# Log the enhanced prompt if logging is enabled
|
212
|
+
if logger:
|
213
|
+
# Use "web_search" role instead of "system" for clearer logs
|
214
|
+
logger.log("web_search", enhanced_prompt.replace(user_input, "").strip())
|
215
|
+
except Exception as e:
|
216
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
217
|
+
# Continue with the original prompt if web search fails
|
191
218
|
|
192
219
|
# Print assistant indicator with formatting - but only if we're not going to show a rich formatted box
|
193
220
|
# With Rich prettify, no header should be printed as the Rich panel already includes it
|
@@ -271,10 +298,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
271
298
|
|
272
299
|
# Get AI response with conversation history
|
273
300
|
response = client.chat(
|
274
|
-
prompt=
|
301
|
+
prompt=enhanced_prompt,
|
275
302
|
messages=conversation,
|
276
303
|
stream=should_stream,
|
277
|
-
web_search=web_search,
|
278
304
|
temperature=temperature,
|
279
305
|
top_p=top_p,
|
280
306
|
max_tokens=max_tokens,
|
ngpt/cli/modes/chat.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
3
|
from ..ui import spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import sys
|
5
6
|
import threading
|
6
7
|
|
@@ -53,6 +54,21 @@ def chat_mode(client, args, logger=None):
|
|
53
54
|
# Log the user message if logging is enabled
|
54
55
|
if logger:
|
55
56
|
logger.log("user", prompt)
|
57
|
+
|
58
|
+
# Enhance prompt with web search if enabled
|
59
|
+
if args.web_search:
|
60
|
+
try:
|
61
|
+
original_prompt = prompt
|
62
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
63
|
+
print("Enhanced input with web search results.")
|
64
|
+
|
65
|
+
# Log the enhanced prompt if logging is enabled
|
66
|
+
if logger:
|
67
|
+
# Use "web_search" role instead of "system" for clearer logs
|
68
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
69
|
+
except Exception as e:
|
70
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
71
|
+
# Continue with the original prompt if web search fails
|
56
72
|
|
57
73
|
# Create messages array with preprompt if available
|
58
74
|
messages = None
|
@@ -123,7 +139,7 @@ def chat_mode(client, args, logger=None):
|
|
123
139
|
if args.stream_prettify and live_display:
|
124
140
|
stream_callback = spinner_handling_callback
|
125
141
|
|
126
|
-
response = client.chat(prompt, stream=should_stream,
|
142
|
+
response = client.chat(prompt, stream=should_stream,
|
127
143
|
temperature=args.temperature, top_p=args.top_p,
|
128
144
|
max_tokens=args.max_tokens, messages=messages,
|
129
145
|
markdown_format=args.prettify or args.stream_prettify,
|
ngpt/cli/modes/code.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
|
3
3
|
from ..ui import spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import sys
|
5
6
|
import threading
|
6
7
|
|
@@ -25,6 +26,21 @@ def code_mode(client, args, logger=None):
|
|
25
26
|
# Log the user prompt if logging is enabled
|
26
27
|
if logger:
|
27
28
|
logger.log("user", prompt)
|
29
|
+
|
30
|
+
# Enhance prompt with web search if enabled
|
31
|
+
if args.web_search:
|
32
|
+
try:
|
33
|
+
original_prompt = prompt
|
34
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
35
|
+
print("Enhanced input with web search results.")
|
36
|
+
|
37
|
+
# Log the enhanced prompt if logging is enabled
|
38
|
+
if logger:
|
39
|
+
# Use "web_search" role instead of "system" for clearer logs
|
40
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
41
|
+
except Exception as e:
|
42
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
43
|
+
# Continue with the original prompt if web search fails
|
28
44
|
|
29
45
|
# Setup for streaming and prettify logic
|
30
46
|
stream_callback = None
|
@@ -110,8 +126,7 @@ def code_mode(client, args, logger=None):
|
|
110
126
|
|
111
127
|
generated_code = client.generate_code(
|
112
128
|
prompt=prompt,
|
113
|
-
language=args.language,
|
114
|
-
web_search=args.web_search,
|
129
|
+
language=args.language,
|
115
130
|
temperature=args.temperature,
|
116
131
|
top_p=args.top_p,
|
117
132
|
max_tokens=args.max_tokens,
|
ngpt/cli/modes/rewrite.py
CHANGED
@@ -5,6 +5,7 @@ import time
|
|
5
5
|
from ..formatters import COLORS
|
6
6
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
7
7
|
from ..ui import get_multiline_input, spinner
|
8
|
+
from ...utils import enhance_prompt_with_web_search
|
8
9
|
|
9
10
|
# System prompt for rewriting text
|
10
11
|
REWRITE_SYSTEM_PROMPT = """You are an expert text editor and rewriter. Your task is to rewrite the user's text to improve readability and flow while carefully preserving the original meaning, tone, and style.
|
@@ -125,6 +126,21 @@ def rewrite_mode(client, args, logger=None):
|
|
125
126
|
print(f"{COLORS['yellow']}Error: Empty input. Please provide text to rewrite.{COLORS['reset']}")
|
126
127
|
return
|
127
128
|
|
129
|
+
# Enhance input with web search if enabled
|
130
|
+
if args.web_search:
|
131
|
+
try:
|
132
|
+
original_text = input_text
|
133
|
+
input_text = enhance_prompt_with_web_search(input_text, logger=logger)
|
134
|
+
print("Enhanced input with web search results.")
|
135
|
+
|
136
|
+
# Log the enhanced input if logging is enabled
|
137
|
+
if logger:
|
138
|
+
# Use "web_search" role instead of "system" for clearer logs
|
139
|
+
logger.log("web_search", input_text.replace(original_text, "").strip())
|
140
|
+
except Exception as e:
|
141
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance input with web search: {str(e)}{COLORS['reset']}")
|
142
|
+
# Continue with the original input if web search fails
|
143
|
+
|
128
144
|
# Set up messages array with system prompt and user content
|
129
145
|
messages = [
|
130
146
|
{"role": "system", "content": REWRITE_SYSTEM_PROMPT},
|
@@ -195,7 +211,6 @@ def rewrite_mode(client, args, logger=None):
|
|
195
211
|
response = client.chat(
|
196
212
|
prompt=None, # Not used when messages are provided
|
197
213
|
stream=should_stream,
|
198
|
-
web_search=args.web_search,
|
199
214
|
temperature=args.temperature,
|
200
215
|
top_p=args.top_p,
|
201
216
|
max_tokens=args.max_tokens,
|
ngpt/cli/modes/shell.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..ui import spinner
|
3
|
+
from ...utils import enhance_prompt_with_web_search
|
3
4
|
import subprocess
|
4
5
|
import sys
|
5
6
|
import threading
|
@@ -26,6 +27,21 @@ def shell_mode(client, args, logger=None):
|
|
26
27
|
if logger:
|
27
28
|
logger.log("user", prompt)
|
28
29
|
|
30
|
+
# Enhance prompt with web search if enabled
|
31
|
+
if args.web_search:
|
32
|
+
try:
|
33
|
+
original_prompt = prompt
|
34
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
35
|
+
print("Enhanced input with web search results.")
|
36
|
+
|
37
|
+
# Log the enhanced prompt if logging is enabled
|
38
|
+
if logger:
|
39
|
+
# Use "web_search" role instead of "system" for clearer logs
|
40
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
41
|
+
except Exception as e:
|
42
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
43
|
+
# Continue with the original prompt if web search fails
|
44
|
+
|
29
45
|
# Start spinner while waiting for command generation
|
30
46
|
stop_spinner = threading.Event()
|
31
47
|
spinner_thread = threading.Thread(
|
@@ -37,9 +53,9 @@ def shell_mode(client, args, logger=None):
|
|
37
53
|
spinner_thread.start()
|
38
54
|
|
39
55
|
try:
|
40
|
-
command = client.generate_shell_command(prompt,
|
41
|
-
|
42
|
-
|
56
|
+
command = client.generate_shell_command(prompt,
|
57
|
+
temperature=args.temperature, top_p=args.top_p,
|
58
|
+
max_tokens=args.max_tokens)
|
43
59
|
finally:
|
44
60
|
# Stop the spinner
|
45
61
|
stop_spinner.set()
|
ngpt/cli/modes/text.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
3
|
from ..ui import get_multiline_input, spinner
|
4
|
+
from ...utils import enhance_prompt_with_web_search
|
4
5
|
import threading
|
5
6
|
import sys
|
6
7
|
|
@@ -25,6 +26,21 @@ def text_mode(client, args, logger=None):
|
|
25
26
|
if logger:
|
26
27
|
logger.log("user", prompt)
|
27
28
|
|
29
|
+
# Enhance prompt with web search if enabled
|
30
|
+
if args.web_search:
|
31
|
+
try:
|
32
|
+
original_prompt = prompt
|
33
|
+
prompt = enhance_prompt_with_web_search(prompt, logger=logger)
|
34
|
+
print("Enhanced input with web search results.")
|
35
|
+
|
36
|
+
# Log the enhanced prompt if logging is enabled
|
37
|
+
if logger:
|
38
|
+
# Use "web_search" role instead of "system" for clearer logs
|
39
|
+
logger.log("web_search", prompt.replace(original_prompt, "").strip())
|
40
|
+
except Exception as e:
|
41
|
+
print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
|
42
|
+
# Continue with the original prompt if web search fails
|
43
|
+
|
28
44
|
# Create messages array with preprompt if available
|
29
45
|
messages = None
|
30
46
|
if args.preprompt:
|
@@ -94,7 +110,7 @@ def text_mode(client, args, logger=None):
|
|
94
110
|
if args.stream_prettify and live_display:
|
95
111
|
stream_callback = spinner_handling_callback
|
96
112
|
|
97
|
-
response = client.chat(prompt, stream=should_stream,
|
113
|
+
response = client.chat(prompt, stream=should_stream,
|
98
114
|
temperature=args.temperature, top_p=args.top_p,
|
99
115
|
max_tokens=args.max_tokens, messages=messages,
|
100
116
|
markdown_format=args.prettify or args.stream_prettify,
|
ngpt/client.py
CHANGED
@@ -32,7 +32,6 @@ class NGPTClient:
|
|
32
32
|
max_tokens: Optional[int] = None,
|
33
33
|
top_p: float = 1.0,
|
34
34
|
messages: Optional[List[Dict[str, str]]] = None,
|
35
|
-
web_search: bool = False,
|
36
35
|
markdown_format: bool = False,
|
37
36
|
stream_callback: Optional[callable] = None,
|
38
37
|
**kwargs
|
@@ -47,7 +46,6 @@ class NGPTClient:
|
|
47
46
|
max_tokens: Maximum number of tokens to generate
|
48
47
|
top_p: Controls diversity via nucleus sampling
|
49
48
|
messages: Optional list of message objects to override default behavior
|
50
|
-
web_search: Whether to enable web search capability
|
51
49
|
markdown_format: If True, allow markdown-formatted responses, otherwise plain text
|
52
50
|
stream_callback: Optional callback function for streaming mode updates
|
53
51
|
**kwargs: Additional arguments to pass to the API
|
@@ -75,10 +73,6 @@ class NGPTClient:
|
|
75
73
|
"top_p": top_p,
|
76
74
|
}
|
77
75
|
|
78
|
-
# Conditionally add web_search
|
79
|
-
if web_search:
|
80
|
-
payload["web_search"] = True
|
81
|
-
|
82
76
|
# Add max_tokens if provided
|
83
77
|
if max_tokens is not None:
|
84
78
|
payload["max_tokens"] = max_tokens
|
@@ -180,7 +174,6 @@ class NGPTClient:
|
|
180
174
|
def generate_shell_command(
|
181
175
|
self,
|
182
176
|
prompt: str,
|
183
|
-
web_search: bool = False,
|
184
177
|
temperature: float = 0.4,
|
185
178
|
top_p: float = 0.95,
|
186
179
|
max_tokens: Optional[int] = None
|
@@ -190,7 +183,6 @@ class NGPTClient:
|
|
190
183
|
|
191
184
|
Args:
|
192
185
|
prompt: Description of the command to generate
|
193
|
-
web_search: Whether to enable web search capability
|
194
186
|
temperature: Controls randomness in the response
|
195
187
|
top_p: Controls diversity via nucleus sampling
|
196
188
|
max_tokens: Maximum number of tokens to generate
|
@@ -241,7 +233,6 @@ Command:"""
|
|
241
233
|
prompt=prompt,
|
242
234
|
stream=False,
|
243
235
|
messages=messages,
|
244
|
-
web_search=web_search,
|
245
236
|
temperature=temperature,
|
246
237
|
top_p=top_p,
|
247
238
|
max_tokens=max_tokens
|
@@ -254,7 +245,6 @@ Command:"""
|
|
254
245
|
self,
|
255
246
|
prompt: str,
|
256
247
|
language: str = "python",
|
257
|
-
web_search: bool = False,
|
258
248
|
temperature: float = 0.4,
|
259
249
|
top_p: float = 0.95,
|
260
250
|
max_tokens: Optional[int] = None,
|
@@ -268,7 +258,6 @@ Command:"""
|
|
268
258
|
Args:
|
269
259
|
prompt: Description of the code to generate
|
270
260
|
language: Programming language to generate code in
|
271
|
-
web_search: Whether to enable web search capability
|
272
261
|
temperature: Controls randomness in the response
|
273
262
|
top_p: Controls diversity via nucleus sampling
|
274
263
|
max_tokens: Maximum number of tokens to generate
|
@@ -315,7 +304,6 @@ Code:"""
|
|
315
304
|
prompt=prompt,
|
316
305
|
stream=stream,
|
317
306
|
messages=messages,
|
318
|
-
web_search=web_search,
|
319
307
|
temperature=temperature,
|
320
308
|
top_p=top_p,
|
321
309
|
max_tokens=max_tokens,
|
ngpt/utils/__init__.py
CHANGED
@@ -22,6 +22,11 @@ from .cli_config import (
|
|
22
22
|
get_cli_config_dir,
|
23
23
|
get_cli_config_path
|
24
24
|
)
|
25
|
+
from .web_search import (
|
26
|
+
enhance_prompt_with_web_search,
|
27
|
+
get_web_search_results,
|
28
|
+
format_web_search_results_for_prompt
|
29
|
+
)
|
25
30
|
|
26
31
|
__all__ = [
|
27
32
|
"create_logger", "Logger",
|
@@ -29,5 +34,6 @@ __all__ = [
|
|
29
34
|
"add_config_entry", "remove_config_entry", "DEFAULT_CONFIG", "DEFAULT_CONFIG_ENTRY",
|
30
35
|
"load_cli_config", "set_cli_config_option", "get_cli_config_option",
|
31
36
|
"unset_cli_config_option", "apply_cli_config", "list_cli_config_options",
|
32
|
-
"CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path"
|
37
|
+
"CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path",
|
38
|
+
"enhance_prompt_with_web_search", "get_web_search_results", "format_web_search_results_for_prompt"
|
33
39
|
]
|
ngpt/utils/web_search.py
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
"""
|
2
|
+
Web search utilities for nGPT using duckduckgo-search and trafilatura.
|
3
|
+
|
4
|
+
This module provides functionality to search the web and extract
|
5
|
+
information from search results to enhance AI prompts.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import re
|
9
|
+
from typing import List, Dict, Any, Optional
|
10
|
+
from duckduckgo_search import DDGS
|
11
|
+
from urllib.parse import urlparse
|
12
|
+
import requests
|
13
|
+
import sys
|
14
|
+
import datetime
|
15
|
+
|
16
|
+
# Get actual logger from global context instead of using standard logging
|
17
|
+
from . import log
|
18
|
+
|
19
|
+
# Use a global variable to store the logger provided during runtime
|
20
|
+
_logger = None
|
21
|
+
|
22
|
+
def set_logger(logger):
|
23
|
+
"""Set the logger to use for this module."""
|
24
|
+
global _logger
|
25
|
+
_logger = logger
|
26
|
+
|
27
|
+
def get_logger():
|
28
|
+
"""Get the current logger or use a default."""
|
29
|
+
if _logger is not None:
|
30
|
+
return _logger
|
31
|
+
else:
|
32
|
+
# Default logging to stderr if no logger provided
|
33
|
+
class DefaultLogger:
|
34
|
+
def info(self, msg): print(f"INFO: {msg}", file=sys.stderr)
|
35
|
+
def error(self, msg): print(f"ERROR: {msg}", file=sys.stderr)
|
36
|
+
def warning(self, msg): print(f"WARNING: {msg}", file=sys.stderr)
|
37
|
+
def debug(self, msg): pass
|
38
|
+
return DefaultLogger()
|
39
|
+
|
40
|
+
def perform_web_search(query: str, max_results: int = 5) -> List[Dict[str, Any]]:
|
41
|
+
"""
|
42
|
+
Search the web using DuckDuckGo and return relevant results.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
query: The search query
|
46
|
+
max_results: Maximum number of results to return
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
List of dictionaries containing search results (title, url, snippet)
|
50
|
+
"""
|
51
|
+
logger = get_logger()
|
52
|
+
try:
|
53
|
+
ddgs = DDGS()
|
54
|
+
results = list(ddgs.text(query, max_results=max_results))
|
55
|
+
return results
|
56
|
+
except Exception as e:
|
57
|
+
logger.error(f"Error performing web search: {str(e)}")
|
58
|
+
logger.info("Web search encountered an issue, but will continue with available results")
|
59
|
+
return []
|
60
|
+
|
61
|
+
def extract_article_content(url: str, max_chars: int = 2000) -> Optional[str]:
|
62
|
+
"""
|
63
|
+
Extract and clean content from a webpage URL.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
url: The URL to extract content from
|
67
|
+
max_chars: Maximum number of characters to extract
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Cleaned article text or None if extraction failed
|
71
|
+
"""
|
72
|
+
logger = get_logger()
|
73
|
+
try:
|
74
|
+
# Skip non-http URLs or suspicious domains
|
75
|
+
parsed_url = urlparse(url)
|
76
|
+
if not parsed_url.scheme.startswith('http'):
|
77
|
+
return None
|
78
|
+
|
79
|
+
# Browser-like user agent
|
80
|
+
headers = {
|
81
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
82
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
83
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
84
|
+
'Connection': 'keep-alive',
|
85
|
+
'Upgrade-Insecure-Requests': '1',
|
86
|
+
}
|
87
|
+
|
88
|
+
logger.info(f"Fetching content from {url}")
|
89
|
+
|
90
|
+
try:
|
91
|
+
# Try using trafilatura
|
92
|
+
import trafilatura
|
93
|
+
|
94
|
+
# Download with correct parameters
|
95
|
+
# trafilatura handles user-agent internally
|
96
|
+
downloaded = trafilatura.fetch_url(url)
|
97
|
+
|
98
|
+
if downloaded:
|
99
|
+
# Extract main content
|
100
|
+
content = trafilatura.extract(downloaded, include_comments=False,
|
101
|
+
include_tables=False,
|
102
|
+
no_fallback=False)
|
103
|
+
|
104
|
+
if content:
|
105
|
+
# Clean up content if needed
|
106
|
+
content = content.strip()
|
107
|
+
|
108
|
+
# Truncate if needed
|
109
|
+
if len(content) > max_chars:
|
110
|
+
content = content[:max_chars] + "..."
|
111
|
+
|
112
|
+
return content
|
113
|
+
|
114
|
+
# If trafilatura failed, try direct requests
|
115
|
+
logger.info(f"Trafilatura extraction failed for {url}, trying fallback method")
|
116
|
+
response = requests.get(url, headers=headers, timeout=10)
|
117
|
+
|
118
|
+
if response.status_code == 200:
|
119
|
+
# Very basic HTML cleaning
|
120
|
+
html_content = response.text
|
121
|
+
# Remove HTML tags
|
122
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
123
|
+
# Remove excess whitespace
|
124
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
125
|
+
|
126
|
+
if text:
|
127
|
+
if len(text) > max_chars:
|
128
|
+
text = text[:max_chars] + "..."
|
129
|
+
return text
|
130
|
+
|
131
|
+
else:
|
132
|
+
logger.error(f"Request to {url} returned status code {response.status_code}")
|
133
|
+
|
134
|
+
except ImportError:
|
135
|
+
logger.error("Trafilatura not installed. Install with 'pip install trafilatura'")
|
136
|
+
# Try direct requests only
|
137
|
+
try:
|
138
|
+
response = requests.get(url, headers=headers, timeout=10)
|
139
|
+
if response.status_code == 200:
|
140
|
+
# Very basic HTML cleaning
|
141
|
+
html_content = response.text
|
142
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
143
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
144
|
+
|
145
|
+
if text:
|
146
|
+
if len(text) > max_chars:
|
147
|
+
text = text[:max_chars] + "..."
|
148
|
+
return text
|
149
|
+
except Exception as req_error:
|
150
|
+
logger.error(f"Direct request fallback failed: {str(req_error)}")
|
151
|
+
|
152
|
+
except Exception as e:
|
153
|
+
logger.error(f"Error extracting content with trafilatura: {str(e)}")
|
154
|
+
# Try the requests fallback
|
155
|
+
try:
|
156
|
+
response = requests.get(url, headers=headers, timeout=10)
|
157
|
+
if response.status_code == 200:
|
158
|
+
html_content = response.text
|
159
|
+
text = re.sub(r'<[^>]+>', ' ', html_content)
|
160
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
161
|
+
|
162
|
+
if text:
|
163
|
+
if len(text) > max_chars:
|
164
|
+
text = text[:max_chars] + "..."
|
165
|
+
return text
|
166
|
+
except Exception as req_error:
|
167
|
+
logger.error(f"Direct request fallback failed: {str(req_error)}")
|
168
|
+
|
169
|
+
return None
|
170
|
+
except Exception as e:
|
171
|
+
logger.error(f"Error extracting content from {url}: {str(e)}")
|
172
|
+
return None
|
173
|
+
|
174
|
+
def get_web_search_results(query: str, max_results: int = 3, max_chars_per_result: int = 2000) -> Dict[str, Any]:
|
175
|
+
"""
|
176
|
+
Get formatted web search results ready to be included in AI prompts.
|
177
|
+
|
178
|
+
Args:
|
179
|
+
query: The search query
|
180
|
+
max_results: Maximum number of results to include
|
181
|
+
max_chars_per_result: Maximum characters to include per result
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
Dictionary containing search results and metadata
|
185
|
+
"""
|
186
|
+
logger = get_logger()
|
187
|
+
search_results = perform_web_search(query, max_results)
|
188
|
+
enhanced_results = []
|
189
|
+
success_count = 0
|
190
|
+
failure_count = 0
|
191
|
+
|
192
|
+
for result in search_results:
|
193
|
+
content = extract_article_content(result['href'], max_chars_per_result)
|
194
|
+
|
195
|
+
enhanced_results.append({
|
196
|
+
'title': result.get('title', ''),
|
197
|
+
'url': result.get('href', ''),
|
198
|
+
'snippet': result.get('body', ''),
|
199
|
+
'content': content if content else result.get('body', '')
|
200
|
+
})
|
201
|
+
|
202
|
+
if content:
|
203
|
+
success_count += 1
|
204
|
+
else:
|
205
|
+
failure_count += 1
|
206
|
+
|
207
|
+
# Log a user-friendly summary
|
208
|
+
if search_results:
|
209
|
+
if failure_count > 0:
|
210
|
+
logger.info(f"Retrieved content from {success_count} out of {len(search_results)} sources")
|
211
|
+
else:
|
212
|
+
logger.info(f"Successfully retrieved content from all {success_count} sources")
|
213
|
+
else:
|
214
|
+
logger.error("No search results were found")
|
215
|
+
|
216
|
+
# Add current timestamp
|
217
|
+
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
218
|
+
|
219
|
+
return {
|
220
|
+
'query': query,
|
221
|
+
'timestamp': current_time,
|
222
|
+
'results': enhanced_results
|
223
|
+
}
|
224
|
+
|
225
|
+
def format_web_search_results_for_prompt(search_results: Dict[str, Any]) -> str:
|
226
|
+
"""
|
227
|
+
Format web search results into a string to include in AI prompts.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
search_results: Dictionary of search results from get_web_search_results()
|
231
|
+
|
232
|
+
Returns:
|
233
|
+
Formatted string to include in prompts
|
234
|
+
"""
|
235
|
+
query = search_results['query']
|
236
|
+
results = search_results['results']
|
237
|
+
timestamp = search_results['timestamp']
|
238
|
+
|
239
|
+
formatted_text = f"[Web Search Results for: {query} (searched at {timestamp})]\n\n"
|
240
|
+
|
241
|
+
for i, result in enumerate(results, 1):
|
242
|
+
formatted_text += f"RESULT {i}: {result['title']}\n"
|
243
|
+
formatted_text += f"URL: {result['url']}\n"
|
244
|
+
formatted_text += f"CONTENT:\n{result['content']}\n\n"
|
245
|
+
|
246
|
+
formatted_text += f"[End of Web Search Results]\n\n"
|
247
|
+
formatted_text += "Use the above web search information to help answer the user's question. When using this information:\n"
|
248
|
+
formatted_text += "1. Use numbered citations in square brackets [1], [2], etc. when presenting information from search results\n"
|
249
|
+
formatted_text += "2. Include a numbered reference list at the end of your response with the source URLs\n"
|
250
|
+
formatted_text += "3. Format citations like 'According to [1]...' or 'Research indicates [2]...' or add citations at the end of sentences or paragraphs\n"
|
251
|
+
formatted_text += "4. If search results contain conflicting information, acknowledge the differences and explain them with citations\n"
|
252
|
+
formatted_text += "5. If the search results don't provide sufficient information, acknowledge the limitations\n"
|
253
|
+
formatted_text += "6. Balance information from multiple sources when appropriate\n"
|
254
|
+
formatted_text += "7. YOU MUST include an empty blockquote line ('>') between each reference in the reference list\n"
|
255
|
+
formatted_text += "8. YOU MUST include ALL available references (between 2-7 sources) in your reference list\n\n"
|
256
|
+
formatted_text += "Example citation format in text:\n"
|
257
|
+
formatted_text += "Today is Thursday [1] and it's expected to rain tomorrow [2].\n\n"
|
258
|
+
formatted_text += "Example reference format (YOU MUST FOLLOW THIS EXACT FORMAT WITH EMPTY LINES BETWEEN REFERENCES):\n"
|
259
|
+
formatted_text += "> [0] https://example.com/date\n"
|
260
|
+
formatted_text += ">\n"
|
261
|
+
formatted_text += "> [1] https://weather.com/forecast\n"
|
262
|
+
formatted_text += ">\n"
|
263
|
+
formatted_text += "> [2] https://www.timeanddate.com\n\n"
|
264
|
+
|
265
|
+
return formatted_text
|
266
|
+
|
267
|
+
def enhance_prompt_with_web_search(prompt: str, max_results: int = 5, logger=None) -> str:
|
268
|
+
"""
|
269
|
+
Enhance a prompt with web search results.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
prompt: The original user prompt
|
273
|
+
max_results: Maximum number of search results to include
|
274
|
+
logger: Optional logger to use
|
275
|
+
|
276
|
+
Returns:
|
277
|
+
Enhanced prompt with web search results prepended
|
278
|
+
"""
|
279
|
+
# Set the logger for this module
|
280
|
+
if logger is not None:
|
281
|
+
set_logger(logger)
|
282
|
+
|
283
|
+
logger = get_logger()
|
284
|
+
search_results = get_web_search_results(prompt, max_results)
|
285
|
+
formatted_results = format_web_search_results_for_prompt(search_results)
|
286
|
+
|
287
|
+
# Combine results with original prompt
|
288
|
+
enhanced_prompt = formatted_results + prompt
|
289
|
+
|
290
|
+
logger.info("Enhanced input with web search results")
|
291
|
+
return enhanced_prompt
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ngpt
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.4.0
|
4
4
|
Summary: Swiss army knife for LLMs: powerful CLI, interactive chatbot, and flexible Python library. Works with OpenAI, Ollama, Groq, Claude, Gemini, and any OpenAI-compatible API.
|
5
5
|
Project-URL: Homepage, https://github.com/nazdridoy/ngpt
|
6
6
|
Project-URL: Repository, https://github.com/nazdridoy/ngpt
|
@@ -28,10 +28,12 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
28
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
29
29
|
Classifier: Topic :: Utilities
|
30
30
|
Requires-Python: >=3.8
|
31
|
+
Requires-Dist: duckduckgo-search>=3.0.0
|
31
32
|
Requires-Dist: prompt-toolkit>=3.0.0
|
32
33
|
Requires-Dist: pyperclip>=1.8.0
|
33
34
|
Requires-Dist: requests>=2.31.0
|
34
35
|
Requires-Dist: rich>=10.0.0
|
36
|
+
Requires-Dist: trafilatura>=1.6.0
|
35
37
|
Description-Content-Type: text/markdown
|
36
38
|
|
37
39
|
# nGPT
|
@@ -63,7 +65,7 @@ Description-Content-Type: text/markdown
|
|
63
65
|
- 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, Claude, Gemini, and any compatible endpoint
|
64
66
|
- 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
|
65
67
|
- 📊 **Streaming Responses**: Real-time output for better user experience
|
66
|
-
- 🔍 **Web Search**:
|
68
|
+
- 🔍 **Web Search**: Enhance any model with contextual information from the web
|
67
69
|
- 📥 **Stdin Processing**: Process piped content by using `{}` placeholder in prompts
|
68
70
|
- 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
|
69
71
|
- ⚡ **Real-time Markdown**: Stream responses with live updating syntax highlighting and formatting
|
@@ -271,7 +273,7 @@ ngpt --list-models --provider Gemini
|
|
271
273
|
# With custom options
|
272
274
|
ngpt --api-key your-key --base-url http://your-endpoint --model your-model "Hello"
|
273
275
|
|
274
|
-
# Enable web search
|
276
|
+
# Enable web search capability to enhance prompts with web information
|
275
277
|
ngpt --web-search "What's the latest news about AI?"
|
276
278
|
|
277
279
|
# Generate and execute shell commands (using -s or --shell flag)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
|
2
|
+
ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
+
ngpt/client.py,sha256=1kn-kVQ2ZYhOlQ5OPM9c_btVKajfk1qb52QbMyGdYtU,14645
|
4
|
+
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
5
|
+
ngpt/cli/args.py,sha256=7ixh5anahTeHWsrEKwhaLA_dR3sJ-ZzO8NyGOeDmd3Q,11392
|
6
|
+
ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
|
7
|
+
ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
|
8
|
+
ngpt/cli/interactive.py,sha256=6SrnANvE5T9Luu-CSGPfW7lub4-yog9aqruAfs1XEIg,16392
|
9
|
+
ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
|
10
|
+
ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
|
11
|
+
ngpt/cli/ui.py,sha256=HoHDFpLiwMBP5wtMb8YYo244FMiqiPFRoBNcNGp6N0A,7310
|
12
|
+
ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
|
13
|
+
ngpt/cli/modes/chat.py,sha256=UlnZWqvxL_CSyVIDZKGVXNlG-KufDfo05E8-8xTcM_Y,6713
|
14
|
+
ngpt/cli/modes/code.py,sha256=4TpIV-EkyXb8d3XJKrsLs9Wxq5P9jC82z94PI2Zck_g,6730
|
15
|
+
ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
|
16
|
+
ngpt/cli/modes/rewrite.py,sha256=ftD-6M9iQ7g4rLdlKyyLTRiJWYtbz64LIG4PIByxmOk,11472
|
17
|
+
ngpt/cli/modes/shell.py,sha256=fxE9LEEo4arSn5-q_6zxdnUH7RlqifWmk-_kcA76OhM,4070
|
18
|
+
ngpt/cli/modes/text.py,sha256=gdn4opioZ6G3nvfrTkp-dpoD-Of_ZvjVVRggVd6edkg,5528
|
19
|
+
ngpt/utils/__init__.py,sha256=qu_66I1Vtav2f1LDiPn5J3DUsbK7o1CSScMcTkYqxoM,1179
|
20
|
+
ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
|
21
|
+
ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
|
22
|
+
ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
|
23
|
+
ngpt/utils/web_search.py,sha256=xHfAymkVkx5b7rOITee2smxpH7waRUpaX8aqTf5WBeA,11599
|
24
|
+
ngpt-3.4.0.dist-info/METADATA,sha256=1xkrTK7mKFEVilhnLdkNqsRmuczpphjRCgSD8t89Gp8,29100
|
25
|
+
ngpt-3.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
26
|
+
ngpt-3.4.0.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
|
27
|
+
ngpt-3.4.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
28
|
+
ngpt-3.4.0.dist-info/RECORD,,
|
ngpt-3.2.0.dist-info/RECORD
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
|
2
|
-
ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
-
ngpt/client.py,sha256=rLgDPmJe8_yi13-XUiHJ45z54rJVrupxWmeb-fQZGF4,15129
|
4
|
-
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
5
|
-
ngpt/cli/args.py,sha256=XQvofZs_WkbgUto3Dbx7Yw-AmPAQHh8kdHUe3uWy8w4,11382
|
6
|
-
ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
|
7
|
-
ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
|
8
|
-
ngpt/cli/interactive.py,sha256=4--9vucwV17n0aH3BNIB4mPGGL6tJYbfL4-h9ZxNezk,14842
|
9
|
-
ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
|
10
|
-
ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
|
11
|
-
ngpt/cli/ui.py,sha256=HoHDFpLiwMBP5wtMb8YYo244FMiqiPFRoBNcNGp6N0A,7310
|
12
|
-
ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
|
13
|
-
ngpt/cli/modes/chat.py,sha256=byOULc1pTl_16IxwRW5FxBgTy_WUF9AJ5TIhdfawzj0,5951
|
14
|
-
ngpt/cli/modes/code.py,sha256=CVZrqlbMS3Weej5xllBhw95qtlOKkNdHGN24trQDesw,5977
|
15
|
-
ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
|
16
|
-
ngpt/cli/modes/rewrite.py,sha256=2h3liGXch6cZyduj1HrEXpHTjKVDDV_zRJwB7fKdoGg,10710
|
17
|
-
ngpt/cli/modes/shell.py,sha256=QkprnOxMMTg2v5DIwcofDnnr3JPNfuk-YgSQaae5Xps,3311
|
18
|
-
ngpt/cli/modes/text.py,sha256=knz4vhOzCW2wbwRmFN2AdSl9twe8IgatlGIx-t7b9ZU,4766
|
19
|
-
ngpt/utils/__init__.py,sha256=E46suk2-QgYBI0Qrs6WXOajOUOebF3ETAFY7ah8DTWs,942
|
20
|
-
ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
|
21
|
-
ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
|
22
|
-
ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
|
23
|
-
ngpt-3.2.0.dist-info/METADATA,sha256=Oyhg9XpBGgDZ4M4wA700C0Vr92lQSVpWd0-OqXe5cHg,28992
|
24
|
-
ngpt-3.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
25
|
-
ngpt-3.2.0.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
|
26
|
-
ngpt-3.2.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
27
|
-
ngpt-3.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|