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.

Files changed (55) hide show
  1. entari_plugin_hyw-4.0.0rc19.dist-info/METADATA +26 -0
  2. entari_plugin_hyw-4.0.0rc19.dist-info/RECORD +4 -0
  3. entari_plugin_hyw-4.0.0rc19.dist-info/top_level.txt +1 -0
  4. entari_plugin_hyw/__init__.py +0 -914
  5. entari_plugin_hyw/filters.py +0 -83
  6. entari_plugin_hyw/history.py +0 -251
  7. entari_plugin_hyw/misc.py +0 -214
  8. entari_plugin_hyw/search_cache.py +0 -253
  9. entari_plugin_hyw-4.0.0rc17.dist-info/METADATA +0 -119
  10. entari_plugin_hyw-4.0.0rc17.dist-info/RECORD +0 -52
  11. entari_plugin_hyw-4.0.0rc17.dist-info/top_level.txt +0 -2
  12. hyw_core/__init__.py +0 -94
  13. hyw_core/agent.py +0 -876
  14. hyw_core/browser_control/__init__.py +0 -63
  15. hyw_core/browser_control/assets/card-dist/index.html +0 -429
  16. hyw_core/browser_control/assets/card-dist/logos/anthropic.svg +0 -1
  17. hyw_core/browser_control/assets/card-dist/logos/cerebras.svg +0 -9
  18. hyw_core/browser_control/assets/card-dist/logos/deepseek.png +0 -0
  19. hyw_core/browser_control/assets/card-dist/logos/gemini.svg +0 -1
  20. hyw_core/browser_control/assets/card-dist/logos/google.svg +0 -1
  21. hyw_core/browser_control/assets/card-dist/logos/grok.png +0 -0
  22. hyw_core/browser_control/assets/card-dist/logos/huggingface.png +0 -0
  23. hyw_core/browser_control/assets/card-dist/logos/microsoft.svg +0 -15
  24. hyw_core/browser_control/assets/card-dist/logos/minimax.png +0 -0
  25. hyw_core/browser_control/assets/card-dist/logos/mistral.png +0 -0
  26. hyw_core/browser_control/assets/card-dist/logos/nvida.png +0 -0
  27. hyw_core/browser_control/assets/card-dist/logos/openai.svg +0 -1
  28. hyw_core/browser_control/assets/card-dist/logos/openrouter.png +0 -0
  29. hyw_core/browser_control/assets/card-dist/logos/perplexity.svg +0 -24
  30. hyw_core/browser_control/assets/card-dist/logos/qwen.png +0 -0
  31. hyw_core/browser_control/assets/card-dist/logos/xai.png +0 -0
  32. hyw_core/browser_control/assets/card-dist/logos/xiaomi.png +0 -0
  33. hyw_core/browser_control/assets/card-dist/logos/zai.png +0 -0
  34. hyw_core/browser_control/assets/card-dist/vite.svg +0 -1
  35. hyw_core/browser_control/engines/__init__.py +0 -15
  36. hyw_core/browser_control/engines/base.py +0 -13
  37. hyw_core/browser_control/engines/default.py +0 -166
  38. hyw_core/browser_control/engines/duckduckgo.py +0 -171
  39. hyw_core/browser_control/landing.html +0 -172
  40. hyw_core/browser_control/manager.py +0 -173
  41. hyw_core/browser_control/renderer.py +0 -446
  42. hyw_core/browser_control/service.py +0 -1002
  43. hyw_core/config.py +0 -154
  44. hyw_core/core.py +0 -454
  45. hyw_core/crawling/__init__.py +0 -18
  46. hyw_core/crawling/completeness.py +0 -437
  47. hyw_core/crawling/models.py +0 -88
  48. hyw_core/definitions.py +0 -166
  49. hyw_core/image_cache.py +0 -274
  50. hyw_core/pipeline.py +0 -502
  51. hyw_core/search.py +0 -169
  52. hyw_core/stages/__init__.py +0 -21
  53. hyw_core/stages/base.py +0 -95
  54. hyw_core/stages/summary.py +0 -218
  55. {entari_plugin_hyw-4.0.0rc17.dist-info → entari_plugin_hyw-4.0.0rc19.dist-info}/WHEEL +0 -0
@@ -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
@@ -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)