entari-plugin-hyw 4.0.0rc17__py3-none-any.whl → 4.0.0rc19__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.
Potentially problematic release.
This version of entari-plugin-hyw might be problematic. Click here for more details.
- entari_plugin_hyw-4.0.0rc19.dist-info/METADATA +26 -0
- entari_plugin_hyw-4.0.0rc19.dist-info/RECORD +4 -0
- entari_plugin_hyw-4.0.0rc19.dist-info/top_level.txt +1 -0
- entari_plugin_hyw/__init__.py +0 -914
- entari_plugin_hyw/filters.py +0 -83
- entari_plugin_hyw/history.py +0 -251
- entari_plugin_hyw/misc.py +0 -214
- entari_plugin_hyw/search_cache.py +0 -253
- entari_plugin_hyw-4.0.0rc17.dist-info/METADATA +0 -119
- entari_plugin_hyw-4.0.0rc17.dist-info/RECORD +0 -52
- entari_plugin_hyw-4.0.0rc17.dist-info/top_level.txt +0 -2
- hyw_core/__init__.py +0 -94
- hyw_core/agent.py +0 -876
- hyw_core/browser_control/__init__.py +0 -63
- hyw_core/browser_control/assets/card-dist/index.html +0 -429
- hyw_core/browser_control/assets/card-dist/logos/anthropic.svg +0 -1
- hyw_core/browser_control/assets/card-dist/logos/cerebras.svg +0 -9
- hyw_core/browser_control/assets/card-dist/logos/deepseek.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/gemini.svg +0 -1
- hyw_core/browser_control/assets/card-dist/logos/google.svg +0 -1
- hyw_core/browser_control/assets/card-dist/logos/grok.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/huggingface.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/microsoft.svg +0 -15
- hyw_core/browser_control/assets/card-dist/logos/minimax.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/mistral.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/nvida.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/openai.svg +0 -1
- hyw_core/browser_control/assets/card-dist/logos/openrouter.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/perplexity.svg +0 -24
- hyw_core/browser_control/assets/card-dist/logos/qwen.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/xai.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/xiaomi.png +0 -0
- hyw_core/browser_control/assets/card-dist/logos/zai.png +0 -0
- hyw_core/browser_control/assets/card-dist/vite.svg +0 -1
- hyw_core/browser_control/engines/__init__.py +0 -15
- hyw_core/browser_control/engines/base.py +0 -13
- hyw_core/browser_control/engines/default.py +0 -166
- hyw_core/browser_control/engines/duckduckgo.py +0 -171
- hyw_core/browser_control/landing.html +0 -172
- hyw_core/browser_control/manager.py +0 -173
- hyw_core/browser_control/renderer.py +0 -446
- hyw_core/browser_control/service.py +0 -1002
- hyw_core/config.py +0 -154
- hyw_core/core.py +0 -454
- hyw_core/crawling/__init__.py +0 -18
- hyw_core/crawling/completeness.py +0 -437
- hyw_core/crawling/models.py +0 -88
- hyw_core/definitions.py +0 -166
- hyw_core/image_cache.py +0 -274
- hyw_core/pipeline.py +0 -502
- hyw_core/search.py +0 -169
- hyw_core/stages/__init__.py +0 -21
- hyw_core/stages/base.py +0 -95
- hyw_core/stages/summary.py +0 -218
- {entari_plugin_hyw-4.0.0rc17.dist-info → entari_plugin_hyw-4.0.0rc19.dist-info}/WHEEL +0 -0
hyw_core/stages/__init__.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
hyw_core.stages - Pipeline Stages
|
|
3
|
-
|
|
4
|
-
This subpackage provides the pipeline stage implementations:
|
|
5
|
-
- BaseStage: Abstract base class for all stages
|
|
6
|
-
- StageContext: Shared context between stages
|
|
7
|
-
- StageResult: Stage execution result
|
|
8
|
-
- InstructStage: Initial task planning and search execution
|
|
9
|
-
- SummaryStage: Final response generation
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from .base import BaseStage, StageContext, StageResult
|
|
13
|
-
|
|
14
|
-
from .summary import SummaryStage
|
|
15
|
-
|
|
16
|
-
__all__ = [
|
|
17
|
-
"BaseStage",
|
|
18
|
-
"StageContext",
|
|
19
|
-
"StageResult",
|
|
20
|
-
"SummaryStage",
|
|
21
|
-
]
|
hyw_core/stages/base.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Stage Base Classes
|
|
3
|
-
|
|
4
|
-
Abstract base classes for pipeline stages.
|
|
5
|
-
Each stage is a self-contained unit of work.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from abc import ABC, abstractmethod
|
|
9
|
-
from dataclasses import dataclass, field
|
|
10
|
-
from typing import Any, Dict, List, Optional
|
|
11
|
-
|
|
12
|
-
from openai import AsyncOpenAI
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class StageContext:
|
|
17
|
-
"""Shared context passed between stages."""
|
|
18
|
-
user_input: str
|
|
19
|
-
images: List[str] = field(default_factory=list)
|
|
20
|
-
conversation_history: List[Dict] = field(default_factory=list)
|
|
21
|
-
instruct_history: List[Dict] = field(default_factory=list) # History for Instruct stage rounds
|
|
22
|
-
|
|
23
|
-
# Accumulated data
|
|
24
|
-
web_results: List[Dict] = field(default_factory=list)
|
|
25
|
-
agent_context: str = ""
|
|
26
|
-
review_context: str = "" # Context passed from Instruct to Review stage
|
|
27
|
-
|
|
28
|
-
# Mode info (set by Instruct stage)
|
|
29
|
-
task_list: List[str] = field(default_factory=list)
|
|
30
|
-
|
|
31
|
-
# Control flags
|
|
32
|
-
should_refuse: bool = False
|
|
33
|
-
refuse_reason: str = ""
|
|
34
|
-
selected_mode: str = "fast" # "fast" or "deepsearch"
|
|
35
|
-
|
|
36
|
-
# ID counter for unified referencing
|
|
37
|
-
global_id_counter: int = 0
|
|
38
|
-
|
|
39
|
-
# Model capabilities
|
|
40
|
-
image_input_supported: bool = True
|
|
41
|
-
|
|
42
|
-
# Search timing
|
|
43
|
-
search_time: float = 0.0
|
|
44
|
-
|
|
45
|
-
def next_id(self) -> int:
|
|
46
|
-
"""Get next global ID."""
|
|
47
|
-
self.global_id_counter += 1
|
|
48
|
-
return self.global_id_counter
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass
|
|
52
|
-
class StageResult:
|
|
53
|
-
"""Result from a stage execution."""
|
|
54
|
-
success: bool
|
|
55
|
-
data: Dict[str, Any] = field(default_factory=dict)
|
|
56
|
-
usage: Dict[str, int] = field(default_factory=lambda: {"input_tokens": 0, "output_tokens": 0})
|
|
57
|
-
trace: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
-
error: Optional[str] = None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class BaseStage(ABC):
|
|
62
|
-
"""Abstract base class for pipeline stages."""
|
|
63
|
-
|
|
64
|
-
def __init__(self, config: Any, search_service: Any, client: AsyncOpenAI):
|
|
65
|
-
self.config = config
|
|
66
|
-
self.search_service = search_service
|
|
67
|
-
self.client = client
|
|
68
|
-
|
|
69
|
-
@property
|
|
70
|
-
@abstractmethod
|
|
71
|
-
def name(self) -> str:
|
|
72
|
-
"""Stage name for logging and tracing."""
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
@abstractmethod
|
|
76
|
-
async def execute(self, context: StageContext) -> StageResult:
|
|
77
|
-
"""
|
|
78
|
-
Execute the stage.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
context: Shared context with accumulated data
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
StageResult with success status, data, usage, and trace info
|
|
85
|
-
"""
|
|
86
|
-
pass
|
|
87
|
-
|
|
88
|
-
def _client_for(self, api_key: Optional[str], base_url: Optional[str]) -> AsyncOpenAI:
|
|
89
|
-
"""Get or create client with custom credentials."""
|
|
90
|
-
if api_key or base_url:
|
|
91
|
-
return AsyncOpenAI(
|
|
92
|
-
base_url=base_url or self.config.base_url,
|
|
93
|
-
api_key=api_key or self.config.api_key
|
|
94
|
-
)
|
|
95
|
-
return self.client
|
hyw_core/stages/summary.py
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Summary Stage
|
|
3
|
-
|
|
4
|
-
Generates final response based on gathered information.
|
|
5
|
-
Different output formats for different modes.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import time
|
|
9
|
-
import re
|
|
10
|
-
from typing import Any, Dict, List, Optional
|
|
11
|
-
|
|
12
|
-
from loguru import logger
|
|
13
|
-
from openai import AsyncOpenAI
|
|
14
|
-
|
|
15
|
-
from .base import BaseStage, StageContext, StageResult
|
|
16
|
-
from ..definitions import SUMMARY_REPORT_SP, IMAGE_CONTEXT_TEMPLATE, get_refuse_answer_tool
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class SummaryStage(BaseStage):
|
|
20
|
-
"""
|
|
21
|
-
Summary Stage: Generate final response.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def name(self) -> str:
|
|
26
|
-
return "Summary"
|
|
27
|
-
|
|
28
|
-
async def execute(
|
|
29
|
-
self,
|
|
30
|
-
context: StageContext,
|
|
31
|
-
images: List[str] = None
|
|
32
|
-
) -> StageResult:
|
|
33
|
-
"""Generate summary."""
|
|
34
|
-
start_time = time.time()
|
|
35
|
-
|
|
36
|
-
# Format context from web results
|
|
37
|
-
web_content = self._format_web_content(context)
|
|
38
|
-
|
|
39
|
-
# Tools
|
|
40
|
-
refuse_tool = get_refuse_answer_tool()
|
|
41
|
-
full_context = f"{context.agent_context}\n\n{web_content}"
|
|
42
|
-
|
|
43
|
-
# Select prompt
|
|
44
|
-
language = getattr(self.config, "language", "Simplified Chinese")
|
|
45
|
-
|
|
46
|
-
system_prompt = SUMMARY_REPORT_SP + f"\n\n用户要求的语言: {language}"
|
|
47
|
-
|
|
48
|
-
# Build Context Message
|
|
49
|
-
context_message = f"## Web Search & Page Content\n\n```context\n{full_context}\n```"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Build user content
|
|
53
|
-
user_text = context.user_input or "..."
|
|
54
|
-
if images:
|
|
55
|
-
# 构建智能图文融合指导
|
|
56
|
-
image_context = IMAGE_CONTEXT_TEMPLATE.format(image_count=len(images))
|
|
57
|
-
user_content: List[Dict[str, Any]] = [{"type": "text", "text": f"{image_context}{user_text}"}]
|
|
58
|
-
for img_b64 in images:
|
|
59
|
-
url = f"data:image/jpeg;base64,{img_b64}" if not img_b64.startswith("data:") else img_b64
|
|
60
|
-
user_content.append({"type": "image_url", "image_url": {"url": url}})
|
|
61
|
-
else:
|
|
62
|
-
user_content = user_text
|
|
63
|
-
|
|
64
|
-
messages = [
|
|
65
|
-
{"role": "system", "content": system_prompt},
|
|
66
|
-
{"role": "user", "content": context_message},
|
|
67
|
-
{"role": "user", "content": user_content}
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
# Get model config
|
|
71
|
-
model_cfg = self.config.get_model_config("main")
|
|
72
|
-
|
|
73
|
-
client = self._client_for(
|
|
74
|
-
api_key=model_cfg.api_key,
|
|
75
|
-
base_url=model_cfg.base_url
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
model = model_cfg.model_name or self.config.model_name
|
|
79
|
-
|
|
80
|
-
# Retry logic for API calls
|
|
81
|
-
max_retries = 2
|
|
82
|
-
response = None
|
|
83
|
-
last_error = None
|
|
84
|
-
|
|
85
|
-
for attempt in range(max_retries + 1):
|
|
86
|
-
try:
|
|
87
|
-
response = await client.chat.completions.create(
|
|
88
|
-
model=model,
|
|
89
|
-
messages=messages,
|
|
90
|
-
temperature=self.config.temperature,
|
|
91
|
-
extra_body=getattr(self.config, "summary_extra_body", None),
|
|
92
|
-
tools=[refuse_tool],
|
|
93
|
-
tool_choice="auto",
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
# Check for valid response
|
|
97
|
-
if response.choices:
|
|
98
|
-
break # Success, exit retry loop
|
|
99
|
-
|
|
100
|
-
# Empty choices - log and retry
|
|
101
|
-
logger.warning(f"SummaryStage: Empty choices response (attempt {attempt + 1}/{max_retries + 1}). Response: {response}")
|
|
102
|
-
last_error = "Invalid API response: no choices returned"
|
|
103
|
-
|
|
104
|
-
if attempt < max_retries:
|
|
105
|
-
import asyncio
|
|
106
|
-
await asyncio.sleep(1) # Wait 1 second before retry
|
|
107
|
-
|
|
108
|
-
except Exception as e:
|
|
109
|
-
logger.error(f"SummaryStage LLM error (attempt {attempt + 1}/{max_retries + 1}): {e}")
|
|
110
|
-
last_error = str(e)
|
|
111
|
-
|
|
112
|
-
if attempt < max_retries:
|
|
113
|
-
import asyncio
|
|
114
|
-
await asyncio.sleep(1) # Wait 1 second before retry
|
|
115
|
-
|
|
116
|
-
# Check if we got a valid response after retries
|
|
117
|
-
if not response or not response.choices:
|
|
118
|
-
logger.error(f"SummaryStage: All retries exhausted. Last error: {last_error}")
|
|
119
|
-
return StageResult(
|
|
120
|
-
success=False,
|
|
121
|
-
error=last_error or "Invalid API response after retries",
|
|
122
|
-
data={"content": f"Error: {last_error or 'API returned invalid response after retries'}"}
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
usage = {"input_tokens": 0, "output_tokens": 0}
|
|
126
|
-
if hasattr(response, "usage") and response.usage:
|
|
127
|
-
usage["input_tokens"] = getattr(response.usage, "prompt_tokens", 0) or 0
|
|
128
|
-
usage["output_tokens"] = getattr(response.usage, "completion_tokens", 0) or 0
|
|
129
|
-
|
|
130
|
-
# Handle Tool Calls (Refusal)
|
|
131
|
-
tool_calls = response.choices[0].message.tool_calls
|
|
132
|
-
if tool_calls:
|
|
133
|
-
for tc in tool_calls:
|
|
134
|
-
if tc.function.name == "refuse_answer":
|
|
135
|
-
import json
|
|
136
|
-
try:
|
|
137
|
-
args = json.loads(tc.function.arguments)
|
|
138
|
-
reason = args.get("reason", "Refused")
|
|
139
|
-
context.should_refuse = True
|
|
140
|
-
context.refuse_reason = reason
|
|
141
|
-
return StageResult(
|
|
142
|
-
success=True,
|
|
143
|
-
data={"content": f"Refused: {reason}"},
|
|
144
|
-
usage=usage,
|
|
145
|
-
trace={"skipped": True, "reason": reason}
|
|
146
|
-
)
|
|
147
|
-
except: pass
|
|
148
|
-
|
|
149
|
-
content = (response.choices[0].message.content or "").strip()
|
|
150
|
-
|
|
151
|
-
return StageResult(
|
|
152
|
-
success=True,
|
|
153
|
-
data={"content": content},
|
|
154
|
-
usage=usage,
|
|
155
|
-
trace={
|
|
156
|
-
"model": model,
|
|
157
|
-
"provider": model_cfg.model_provider or "Unknown",
|
|
158
|
-
"usage": usage,
|
|
159
|
-
"system_prompt": system_prompt,
|
|
160
|
-
"context_message": context_message, # Includes vision description + search results
|
|
161
|
-
"output": content,
|
|
162
|
-
"time": time.time() - start_time,
|
|
163
|
-
"images_count": len(images) if images else 0,
|
|
164
|
-
}
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
def _strip_links(self, text: str) -> str:
|
|
168
|
-
"""Strip markdown links [text](url) -> text and remove bare URLs."""
|
|
169
|
-
# Replace [text](url) with text
|
|
170
|
-
text = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', text)
|
|
171
|
-
# Remove bare URLs (http/https) roughly, trying to preserve surrounding text if possible?
|
|
172
|
-
# A simple pattern for http/s
|
|
173
|
-
text = re.sub(r'https?://\S+', '', text)
|
|
174
|
-
return text
|
|
175
|
-
|
|
176
|
-
def _format_web_content(self, context: StageContext) -> str:
|
|
177
|
-
"""Format web results for summary prompt."""
|
|
178
|
-
if not context.web_results:
|
|
179
|
-
return ""
|
|
180
|
-
|
|
181
|
-
# Sort results: pages first, then raw searches, then snippets
|
|
182
|
-
def get_priority(item_type):
|
|
183
|
-
if item_type == "page": return 0
|
|
184
|
-
if item_type == "search_raw_page": return 1
|
|
185
|
-
return 2 # search (snippets)
|
|
186
|
-
|
|
187
|
-
sorted_results = sorted(
|
|
188
|
-
context.web_results,
|
|
189
|
-
key=lambda x: get_priority(x.get("_type"))
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
lines = []
|
|
193
|
-
seen_urls = set()
|
|
194
|
-
|
|
195
|
-
for res in sorted_results:
|
|
196
|
-
type_ = res.get("_type")
|
|
197
|
-
idx = res.get("_id")
|
|
198
|
-
title = (res.get("title", "") or "").strip()
|
|
199
|
-
url = res.get("url", "")
|
|
200
|
-
|
|
201
|
-
# Deduplicate items by URL (keep higher priority item only)
|
|
202
|
-
if url:
|
|
203
|
-
if url in seen_urls:
|
|
204
|
-
continue
|
|
205
|
-
seen_urls.add(url)
|
|
206
|
-
|
|
207
|
-
# url = res.get("url", "") # Removed as requested
|
|
208
|
-
|
|
209
|
-
if type_ == "page":
|
|
210
|
-
content = (res.get("content", "") or "").strip()
|
|
211
|
-
content = self._strip_links(content)
|
|
212
|
-
lines.append(f"[{idx}] Title: {title}\nContent:\n{content}\n")
|
|
213
|
-
elif type_ == "search":
|
|
214
|
-
snippet = (res.get("content", "") or "").strip()
|
|
215
|
-
snippet = self._strip_links(snippet)
|
|
216
|
-
lines.append(f"[{idx}] Title: {title}\nSnippet: {snippet}\n")
|
|
217
|
-
|
|
218
|
-
return "\n".join(lines)
|
|
File without changes
|