entari-plugin-hyw 3.3.6__py3-none-any.whl → 3.3.8__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/__init__.py +10 -10
- entari_plugin_hyw/assets/icon/huggingface.png +0 -0
- entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
- entari_plugin_hyw/core/config.py +8 -8
- entari_plugin_hyw/core/pipeline.py +107 -82
- entari_plugin_hyw/core/render.py +27 -0
- entari_plugin_hyw/utils/prompts.py +78 -78
- entari_plugin_hyw/utils/prompts_cn.py +140 -0
- entari_plugin_hyw-3.3.8.dist-info/METADATA +113 -0
- {entari_plugin_hyw-3.3.6.dist-info → entari_plugin_hyw-3.3.8.dist-info}/RECORD +12 -9
- entari_plugin_hyw-3.3.6.dist-info/METADATA +0 -143
- {entari_plugin_hyw-3.3.6.dist-info → entari_plugin_hyw-3.3.8.dist-info}/WHEEL +0 -0
- {entari_plugin_hyw-3.3.6.dist-info → entari_plugin_hyw-3.3.8.dist-info}/top_level.txt +0 -0
entari_plugin_hyw/__init__.py
CHANGED
|
@@ -58,13 +58,11 @@ class HywConfig(BasicConfModel):
|
|
|
58
58
|
base_url: str = "https://openrouter.ai/api/v1"
|
|
59
59
|
vision_model_name: Optional[str] = None
|
|
60
60
|
vision_api_key: Optional[str] = None
|
|
61
|
+
language: str = "Simplified Chinese"
|
|
61
62
|
vision_base_url: Optional[str] = None
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
intruct_base_url: Optional[str] = None
|
|
66
|
-
intruct_system_prompt: Optional[str] = None
|
|
67
|
-
agent_system_prompt: Optional[str] = None
|
|
63
|
+
instruct_model_name: Optional[str] = None
|
|
64
|
+
instruct_api_key: Optional[str] = None
|
|
65
|
+
instruct_base_url: Optional[str] = None
|
|
68
66
|
search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
|
|
69
67
|
image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
|
|
70
68
|
headless: bool = False
|
|
@@ -72,8 +70,10 @@ class HywConfig(BasicConfModel):
|
|
|
72
70
|
icon: str = "openai"
|
|
73
71
|
render_timeout_ms: int = 6000
|
|
74
72
|
extra_body: Optional[Dict[str, Any]] = None
|
|
73
|
+
vision_extra_body: Optional[Dict[str, Any]] = None
|
|
74
|
+
instruct_extra_body: Optional[Dict[str, Any]] = None
|
|
75
75
|
enable_browser_fallback: bool = False
|
|
76
|
-
reaction: bool =
|
|
76
|
+
reaction: bool = False
|
|
77
77
|
quote: bool = True
|
|
78
78
|
temperature: float = 0.4
|
|
79
79
|
# Billing configuration (price per million tokens)
|
|
@@ -83,14 +83,14 @@ class HywConfig(BasicConfModel):
|
|
|
83
83
|
vision_input_price: Optional[float] = None
|
|
84
84
|
vision_output_price: Optional[float] = None
|
|
85
85
|
# Instruct model pricing overrides (defaults to main model pricing if not set)
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
instruct_input_price: Optional[float] = None
|
|
87
|
+
instruct_output_price: Optional[float] = None
|
|
88
88
|
# Provider Names
|
|
89
89
|
search_name: str = "DuckDuckGo"
|
|
90
90
|
search_provider: str = "crawl4ai" # crawl4ai | httpx | ddgs
|
|
91
91
|
model_provider: Optional[str] = None
|
|
92
92
|
vision_model_provider: Optional[str] = None
|
|
93
|
-
|
|
93
|
+
instruct_model_provider: Optional[str] = None
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
|
|
Binary file
|
|
Binary file
|
entari_plugin_hyw/core/config.py
CHANGED
|
@@ -12,27 +12,27 @@ class HYWConfig:
|
|
|
12
12
|
fusion_mode: bool = False
|
|
13
13
|
save_conversation: bool = False
|
|
14
14
|
headless: bool = True
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
instruct_model_name: Optional[str] = None
|
|
16
|
+
instruct_api_key: Optional[str] = None
|
|
17
|
+
instruct_base_url: Optional[str] = None
|
|
18
18
|
search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
|
|
19
19
|
image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
|
|
20
20
|
search_params: Optional[str] = None # e.g. "&kl=cn-zh" for China region
|
|
21
21
|
search_limit: int = 8
|
|
22
22
|
extra_body: Optional[Dict[str, Any]] = None
|
|
23
|
+
vision_extra_body: Optional[Dict[str, Any]] = None
|
|
24
|
+
instruct_extra_body: Optional[Dict[str, Any]] = None
|
|
23
25
|
temperature: float = 0.4
|
|
24
26
|
max_turns: int = 10
|
|
25
27
|
icon: str = "openai" # logo for primary model
|
|
26
28
|
vision_icon: Optional[str] = None # logo for vision model (falls back to icon when absent)
|
|
27
29
|
instruct_icon: Optional[str] = None # logo for instruct model
|
|
28
30
|
enable_browser_fallback: bool = False
|
|
29
|
-
|
|
30
|
-
intruct_system_prompt: Optional[str] = None
|
|
31
|
-
agent_system_prompt: Optional[str] = None
|
|
31
|
+
language: str = "Simplified Chinese"
|
|
32
32
|
input_price: Optional[float] = None # $ per 1M input tokens
|
|
33
33
|
output_price: Optional[float] = None # $ per 1M output tokens
|
|
34
34
|
vision_input_price: Optional[float] = None
|
|
35
35
|
vision_output_price: Optional[float] = None
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
instruct_input_price: Optional[float] = None
|
|
37
|
+
instruct_output_price: Optional[float] = None
|
|
38
38
|
|
|
@@ -12,14 +12,14 @@ from .config import HYWConfig
|
|
|
12
12
|
from ..utils.search import SearchService
|
|
13
13
|
from ..utils.prompts import (
|
|
14
14
|
AGENT_SP,
|
|
15
|
-
|
|
15
|
+
AGENT_SP_INSTRUCT_VISION_ADD,
|
|
16
16
|
AGENT_SP_TOOLS_STANDARD_ADD,
|
|
17
17
|
AGENT_SP_TOOLS_AGENT_ADD,
|
|
18
18
|
AGENT_SP_SEARCH_ADD,
|
|
19
19
|
AGENT_SP_PAGE_ADD,
|
|
20
20
|
AGENT_SP_IMAGE_SEARCH_ADD,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
INSTRUCT_SP,
|
|
22
|
+
INSTRUCT_SP_VISION_ADD,
|
|
23
23
|
VISION_SP,
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -109,7 +109,7 @@ class ProcessingPipeline:
|
|
|
109
109
|
) -> Dict[str, Any]:
|
|
110
110
|
"""
|
|
111
111
|
1) Vision: summarize images once (no image persistence).
|
|
112
|
-
2)
|
|
112
|
+
2) Instruct: run web_search and decide whether to grant Playwright MCP tools.
|
|
113
113
|
3) Agent: normally no tools; if granted, allow Playwright MCP tools (max 6 rounds; step 5 nudge, step 6 forced).
|
|
114
114
|
"""
|
|
115
115
|
start_time = time.time()
|
|
@@ -133,7 +133,7 @@ class ProcessingPipeline:
|
|
|
133
133
|
|
|
134
134
|
trace: Dict[str, Any] = {
|
|
135
135
|
"vision": None,
|
|
136
|
-
"
|
|
136
|
+
"instruct": None,
|
|
137
137
|
"agent": None,
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -150,8 +150,7 @@ class ProcessingPipeline:
|
|
|
150
150
|
or getattr(self.config, "vision_model_name", None)
|
|
151
151
|
or active_model
|
|
152
152
|
)
|
|
153
|
-
|
|
154
|
-
vision_prompt = vision_prompt_tpl.format(user_msgs=user_input or "[图片]")
|
|
153
|
+
vision_prompt = VISION_SP.format(user_msgs=user_input or "[图片]")
|
|
155
154
|
vision_text, vision_usage = await self._run_vision_stage(
|
|
156
155
|
user_input=user_input,
|
|
157
156
|
images=images,
|
|
@@ -182,10 +181,10 @@ class ProcessingPipeline:
|
|
|
182
181
|
"cost": vision_cost
|
|
183
182
|
}
|
|
184
183
|
|
|
185
|
-
#
|
|
184
|
+
# Instruct + pre-search
|
|
186
185
|
instruct_start = time.time()
|
|
187
|
-
instruct_model = getattr(self.config, "
|
|
188
|
-
instruct_text, search_payloads,
|
|
186
|
+
instruct_model = getattr(self.config, "instruct_model_name", None) or active_model
|
|
187
|
+
instruct_text, search_payloads, instruct_trace, instruct_usage, search_time = await self._run_instruct_stage(
|
|
189
188
|
user_input=user_input,
|
|
190
189
|
vision_text=vision_text,
|
|
191
190
|
model=instruct_model,
|
|
@@ -194,24 +193,24 @@ class ProcessingPipeline:
|
|
|
194
193
|
|
|
195
194
|
# Calculate Instruct Cost
|
|
196
195
|
instruct_cost = 0.0
|
|
197
|
-
i_in_price = float(getattr(self.config, "
|
|
198
|
-
i_out_price = float(getattr(self.config, "
|
|
196
|
+
i_in_price = float(getattr(self.config, "instruct_input_price", None) or getattr(self.config, "input_price", 0.0) or 0.0)
|
|
197
|
+
i_out_price = float(getattr(self.config, "instruct_output_price", None) or getattr(self.config, "output_price", 0.0) or 0.0)
|
|
199
198
|
if i_in_price > 0 or i_out_price > 0:
|
|
200
|
-
instruct_cost = (
|
|
199
|
+
instruct_cost = (instruct_usage.get("input_tokens", 0) / 1_000_000 * i_in_price) + (instruct_usage.get("output_tokens", 0) / 1_000_000 * i_out_price)
|
|
201
200
|
|
|
202
201
|
# Add instruct usage
|
|
203
|
-
usage_totals["input_tokens"] +=
|
|
204
|
-
usage_totals["output_tokens"] +=
|
|
202
|
+
usage_totals["input_tokens"] += instruct_usage.get("input_tokens", 0)
|
|
203
|
+
usage_totals["output_tokens"] += instruct_usage.get("output_tokens", 0)
|
|
205
204
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
trace["
|
|
205
|
+
instruct_trace["time"] = instruct_time
|
|
206
|
+
instruct_trace["cost"] = instruct_cost
|
|
207
|
+
trace["instruct"] = instruct_trace
|
|
209
208
|
|
|
210
209
|
# Start agent loop
|
|
211
210
|
agent_start_time = time.time()
|
|
212
211
|
current_history.append({"role": "user", "content": user_input or "..."})
|
|
213
212
|
|
|
214
|
-
mode =
|
|
213
|
+
mode = instruct_trace.get("mode", self.current_mode).lower()
|
|
215
214
|
logger.success(f"Instruct Mode: {mode}")
|
|
216
215
|
self.current_mode = mode
|
|
217
216
|
|
|
@@ -255,18 +254,17 @@ class ProcessingPipeline:
|
|
|
255
254
|
has_image_results = any(r.get("_type") == "image" for r in self.all_web_results)
|
|
256
255
|
|
|
257
256
|
# Build agent system prompt
|
|
258
|
-
agent_prompt_tpl = getattr(self.config, "agent_system_prompt", None) or AGENT_SP
|
|
259
|
-
|
|
260
257
|
mode_desc_text = AGENT_SP_TOOLS_AGENT_ADD.format(tools_desc=tools_desc) if mode == "agent" else AGENT_SP_TOOLS_STANDARD_ADD
|
|
261
|
-
system_prompt =
|
|
258
|
+
system_prompt = AGENT_SP.format(
|
|
262
259
|
user_msgs=user_msgs_text,
|
|
263
260
|
mode=mode,
|
|
264
|
-
mode_desc=mode_desc_text
|
|
261
|
+
mode_desc=mode_desc_text,
|
|
262
|
+
language=getattr(self.config, "language", "Simplified Chinese")[:128]
|
|
265
263
|
)
|
|
266
264
|
|
|
267
265
|
# Append vision text if available
|
|
268
266
|
if vision_text:
|
|
269
|
-
system_prompt +=
|
|
267
|
+
system_prompt += AGENT_SP_INSTRUCT_VISION_ADD.format(vision_msgs=vision_text)
|
|
270
268
|
|
|
271
269
|
# Append search results
|
|
272
270
|
if has_search_results and search_msgs_text:
|
|
@@ -299,6 +297,7 @@ class ProcessingPipeline:
|
|
|
299
297
|
model=active_model,
|
|
300
298
|
tools=tools_for_step,
|
|
301
299
|
tool_choice="auto" if tools_for_step else None,
|
|
300
|
+
extra_body=self.config.extra_body,
|
|
302
301
|
)
|
|
303
302
|
step_llm_time = time.time() - step_llm_start
|
|
304
303
|
|
|
@@ -366,8 +365,8 @@ class ProcessingPipeline:
|
|
|
366
365
|
a_in_price = float(getattr(self.config, "input_price", 0.0) or 0.0)
|
|
367
366
|
a_out_price = float(getattr(self.config, "output_price", 0.0) or 0.0)
|
|
368
367
|
|
|
369
|
-
agent_input_tokens = usage_totals["input_tokens"] - vision_usage.get("input_tokens", 0) -
|
|
370
|
-
agent_output_tokens = usage_totals["output_tokens"] - vision_usage.get("output_tokens", 0) -
|
|
368
|
+
agent_input_tokens = usage_totals["input_tokens"] - vision_usage.get("input_tokens", 0) - instruct_usage.get("input_tokens", 0)
|
|
369
|
+
agent_output_tokens = usage_totals["output_tokens"] - vision_usage.get("output_tokens", 0) - instruct_usage.get("output_tokens", 0)
|
|
371
370
|
|
|
372
371
|
if a_in_price > 0 or a_out_price > 0:
|
|
373
372
|
agent_cost = (max(0, agent_input_tokens) / 1_000_000 * a_in_price) + (max(0, agent_output_tokens) / 1_000_000 * a_out_price)
|
|
@@ -436,14 +435,14 @@ class ProcessingPipeline:
|
|
|
436
435
|
"cost": v.get("cost", 0.0)
|
|
437
436
|
})
|
|
438
437
|
|
|
439
|
-
if trace.get("
|
|
440
|
-
i = trace["
|
|
438
|
+
if trace.get("instruct"):
|
|
439
|
+
i = trace["instruct"]
|
|
441
440
|
i_model = i.get("model", "")
|
|
442
441
|
i_base_url = i.get("base_url", "") or self.config.base_url
|
|
443
442
|
stages_used.append({
|
|
444
443
|
"name": "Instruct",
|
|
445
444
|
"model": i_model,
|
|
446
|
-
"icon_config": getattr(self.config, "instruct_icon", None) or
|
|
445
|
+
"icon_config": getattr(self.config, "instruct_icon", None) or infer_icon(i_model, i_base_url),
|
|
447
446
|
"provider": infer_provider(i_base_url),
|
|
448
447
|
"time": i.get("time", 0),
|
|
449
448
|
"cost": i.get("cost", 0.0)
|
|
@@ -460,9 +459,9 @@ class ProcessingPipeline:
|
|
|
460
459
|
})
|
|
461
460
|
|
|
462
461
|
# Add Crawler stage if Instruct used crawl_page
|
|
463
|
-
if trace.get("
|
|
464
|
-
|
|
465
|
-
crawl_calls = [tc for tc in
|
|
462
|
+
if trace.get("instruct"):
|
|
463
|
+
instruct_tool_calls = trace["instruct"].get("tool_calls", [])
|
|
464
|
+
crawl_calls = [tc for tc in instruct_tool_calls if tc.get("name") == "crawl_page"]
|
|
466
465
|
if crawl_calls:
|
|
467
466
|
# Build crawled_pages list for UI
|
|
468
467
|
crawled_pages = []
|
|
@@ -647,12 +646,7 @@ class ProcessingPipeline:
|
|
|
647
646
|
except Exception:
|
|
648
647
|
pass
|
|
649
648
|
|
|
650
|
-
# 2.
|
|
651
|
-
ref_block_match = re.search(r'```references\s*(.*?)\s*```', remaining_text, re.DOTALL | re.IGNORECASE)
|
|
652
|
-
if ref_block_match:
|
|
653
|
-
remaining_text = remaining_text.replace(ref_block_match.group(0), "").strip()
|
|
654
|
-
|
|
655
|
-
# 3. Scan text for [type:id] tags and rebuild references in order of appearance
|
|
649
|
+
# 2. Extract references from text first (Order by appearance)
|
|
656
650
|
# Pattern matches [search:123], [page:123], [image:123]
|
|
657
651
|
pattern = re.compile(r'\[(search|page|image):(\d+)\]', re.IGNORECASE)
|
|
658
652
|
|
|
@@ -662,24 +656,12 @@ class ProcessingPipeline:
|
|
|
662
656
|
page_map = {}
|
|
663
657
|
image_map = {}
|
|
664
658
|
|
|
665
|
-
|
|
666
|
-
tag_type = m.group(1).lower()
|
|
667
|
-
old_id_str = m.group(2)
|
|
668
|
-
try:
|
|
669
|
-
old_id = int(old_id_str)
|
|
670
|
-
except ValueError:
|
|
671
|
-
continue
|
|
672
|
-
|
|
673
|
-
# Check if we already processed this ID for this type
|
|
674
|
-
if tag_type == "search" and old_id_str in search_map: continue
|
|
675
|
-
if tag_type == "page" and old_id_str in page_map: continue
|
|
676
|
-
if tag_type == "image" and old_id_str in image_map: continue
|
|
677
|
-
|
|
659
|
+
def process_ref(tag_type, old_id):
|
|
678
660
|
# Find in all_web_results
|
|
679
661
|
result_item = next((r for r in self.all_web_results if r.get("_id") == old_id and r.get("_type") == tag_type), None)
|
|
680
662
|
|
|
681
663
|
if not result_item:
|
|
682
|
-
|
|
664
|
+
return
|
|
683
665
|
|
|
684
666
|
entry = {
|
|
685
667
|
"title": result_item.get("title", ""),
|
|
@@ -690,15 +672,56 @@ class ProcessingPipeline:
|
|
|
690
672
|
entry["thumbnail"] = result_item.get("thumbnail", "")
|
|
691
673
|
|
|
692
674
|
# Add to respective list and map
|
|
675
|
+
# Check maps to avoid duplicates
|
|
693
676
|
if tag_type == "search":
|
|
694
|
-
|
|
695
|
-
|
|
677
|
+
if str(old_id) not in search_map:
|
|
678
|
+
parsed["references"].append(entry)
|
|
679
|
+
search_map[str(old_id)] = len(parsed["references"])
|
|
696
680
|
elif tag_type == "page":
|
|
697
|
-
|
|
698
|
-
|
|
681
|
+
if str(old_id) not in page_map:
|
|
682
|
+
parsed["page_references"].append(entry)
|
|
683
|
+
page_map[str(old_id)] = len(parsed["page_references"])
|
|
699
684
|
elif tag_type == "image":
|
|
700
|
-
|
|
701
|
-
|
|
685
|
+
if str(old_id) not in image_map:
|
|
686
|
+
parsed["image_references"].append(entry)
|
|
687
|
+
image_map[str(old_id)] = len(parsed["image_references"])
|
|
688
|
+
|
|
689
|
+
# Pass 1: Text Body
|
|
690
|
+
for m in matches:
|
|
691
|
+
try:
|
|
692
|
+
process_ref(m.group(1).lower(), int(m.group(2)))
|
|
693
|
+
except ValueError:
|
|
694
|
+
continue
|
|
695
|
+
|
|
696
|
+
# 3. Pass 2: References Block (Capture items missed in text)
|
|
697
|
+
ref_block_match = re.search(r'```references\s*(.*?)\s*```', remaining_text, re.DOTALL | re.IGNORECASE)
|
|
698
|
+
if ref_block_match:
|
|
699
|
+
ref_content = ref_block_match.group(1).strip()
|
|
700
|
+
remaining_text = remaining_text.replace(ref_block_match.group(0), "").strip()
|
|
701
|
+
|
|
702
|
+
for line in ref_content.split("\n"):
|
|
703
|
+
line = line.strip()
|
|
704
|
+
if not line: continue
|
|
705
|
+
# Match [id] [type]
|
|
706
|
+
# e.g. [1] [image] ... or [image:1] ...
|
|
707
|
+
|
|
708
|
+
# Check for [id] [type] format
|
|
709
|
+
id_match = re.match(r"^\[(\d+)\]\s*\[(search|page|image)\]", line, re.IGNORECASE)
|
|
710
|
+
if id_match:
|
|
711
|
+
try:
|
|
712
|
+
process_ref(id_match.group(2).lower(), int(id_match.group(1)))
|
|
713
|
+
except ValueError:
|
|
714
|
+
pass
|
|
715
|
+
else:
|
|
716
|
+
# Check for [type:id] format in list
|
|
717
|
+
alt_match = re.match(r"^\[(search|page|image):(\d+)\]", line, re.IGNORECASE)
|
|
718
|
+
if alt_match:
|
|
719
|
+
try:
|
|
720
|
+
process_ref(alt_match.group(1).lower(), int(alt_match.group(2)))
|
|
721
|
+
except ValueError:
|
|
722
|
+
pass
|
|
723
|
+
|
|
724
|
+
# 4. Replace tags in text with new sequential IDs
|
|
702
725
|
|
|
703
726
|
# 4. Replace tags in text with new sequential IDs
|
|
704
727
|
def replace_tag(match):
|
|
@@ -804,10 +827,10 @@ class ProcessingPipeline:
|
|
|
804
827
|
return f"Unknown tool {name}"
|
|
805
828
|
|
|
806
829
|
|
|
807
|
-
async def _safe_llm_call(self, messages, model, tools=None, tool_choice=None, client: Optional[AsyncOpenAI] = None):
|
|
830
|
+
async def _safe_llm_call(self, messages, model, tools=None, tool_choice=None, client: Optional[AsyncOpenAI] = None, extra_body: Optional[Dict[str, Any]] = None):
|
|
808
831
|
try:
|
|
809
832
|
return await asyncio.wait_for(
|
|
810
|
-
self._do_llm_request(messages, model, tools, tool_choice, client=client or self.client),
|
|
833
|
+
self._do_llm_request(messages, model, tools, tool_choice, client=client or self.client, extra_body=extra_body),
|
|
811
834
|
timeout=120.0,
|
|
812
835
|
)
|
|
813
836
|
except asyncio.TimeoutError:
|
|
@@ -817,7 +840,7 @@ class ProcessingPipeline:
|
|
|
817
840
|
logger.error(f"LLM Call Failed: {e}")
|
|
818
841
|
return type("obj", (object,), {"content": f"Error: Model failure ({e})", "tool_calls": None})(), {"input_tokens": 0, "output_tokens": 0}
|
|
819
842
|
|
|
820
|
-
async def _do_llm_request(self, messages, model, tools, tool_choice, client: AsyncOpenAI):
|
|
843
|
+
async def _do_llm_request(self, messages, model, tools, tool_choice, client: AsyncOpenAI, extra_body: Optional[Dict[str, Any]] = None):
|
|
821
844
|
try:
|
|
822
845
|
payload_debug = json.dumps(messages)
|
|
823
846
|
logger.info(f"LLM Request Payload Size: {len(payload_debug)} chars")
|
|
@@ -832,6 +855,7 @@ class ProcessingPipeline:
|
|
|
832
855
|
tools=tools,
|
|
833
856
|
tool_choice=tool_choice,
|
|
834
857
|
temperature=self.config.temperature,
|
|
858
|
+
extra_body=extra_body,
|
|
835
859
|
)
|
|
836
860
|
logger.info(f"LLM Request RECEIVED after {time.time() - t0:.2f}s")
|
|
837
861
|
|
|
@@ -856,6 +880,7 @@ class ProcessingPipeline:
|
|
|
856
880
|
messages=[{"role": "system", "content": prompt}, {"role": "user", "content": content_payload}],
|
|
857
881
|
model=model,
|
|
858
882
|
client=client,
|
|
883
|
+
extra_body=getattr(self.config, "vision_extra_body", None),
|
|
859
884
|
)
|
|
860
885
|
return (response.content or "").strip(), usage
|
|
861
886
|
|
|
@@ -867,15 +892,14 @@ class ProcessingPipeline:
|
|
|
867
892
|
tools = [self.web_search_tool, self.image_search_tool, self.set_mode_tool, self.crawl_page_tool]
|
|
868
893
|
tools_desc = "- internal_web_search: 搜索文本\n- internal_image_search: 搜索图片\n- crawl_page: 获取网页内容\n- set_mode: 设定standard/agent模式"
|
|
869
894
|
|
|
870
|
-
|
|
871
|
-
prompt = prompt_tpl.format(user_msgs=user_input or "", tools_desc=tools_desc)
|
|
895
|
+
prompt = INSTRUCT_SP.format(user_msgs=user_input or "", tools_desc=tools_desc)
|
|
872
896
|
|
|
873
897
|
if vision_text:
|
|
874
|
-
prompt = f"{prompt}\\n\\n{
|
|
898
|
+
prompt = f"{prompt}\\n\\n{INSTRUCT_SP_VISION_ADD.format(vision_msgs=vision_text)}"
|
|
875
899
|
|
|
876
900
|
client = self._client_for(
|
|
877
|
-
api_key=getattr(self.config, "
|
|
878
|
-
base_url=getattr(self.config, "
|
|
901
|
+
api_key=getattr(self.config, "instruct_api_key", None),
|
|
902
|
+
base_url=getattr(self.config, "instruct_base_url", None),
|
|
879
903
|
)
|
|
880
904
|
|
|
881
905
|
history: List[Dict[str, Any]] = [
|
|
@@ -889,12 +913,13 @@ class ProcessingPipeline:
|
|
|
889
913
|
tools=tools,
|
|
890
914
|
tool_choice="auto",
|
|
891
915
|
client=client,
|
|
916
|
+
extra_body=getattr(self.config, "instruct_extra_body", None),
|
|
892
917
|
)
|
|
893
918
|
|
|
894
919
|
search_payloads: List[str] = []
|
|
895
|
-
|
|
920
|
+
instruct_trace: Dict[str, Any] = {
|
|
896
921
|
"model": model,
|
|
897
|
-
"base_url": getattr(self.config, "
|
|
922
|
+
"base_url": getattr(self.config, "instruct_base_url", None) or self.config.base_url,
|
|
898
923
|
"prompt": prompt,
|
|
899
924
|
"user_input": user_input or "",
|
|
900
925
|
"vision_add": vision_text or "",
|
|
@@ -922,8 +947,8 @@ class ProcessingPipeline:
|
|
|
922
947
|
history.append(
|
|
923
948
|
{"tool_call_id": tc.id, "role": "tool", "name": tc.function.name, "content": str(result)}
|
|
924
949
|
)
|
|
925
|
-
|
|
926
|
-
|
|
950
|
+
instruct_trace["tool_calls"].append(self._tool_call_to_trace(tc))
|
|
951
|
+
instruct_trace["tool_results"].append({"name": tc.function.name, "content": str(result)})
|
|
927
952
|
|
|
928
953
|
if tc.function.name in ["web_search", "internal_web_search"]:
|
|
929
954
|
search_payloads.append(str(result))
|
|
@@ -935,18 +960,18 @@ class ProcessingPipeline:
|
|
|
935
960
|
mode = args.get("mode", mode)
|
|
936
961
|
mode_reason = args.get("reason", "")
|
|
937
962
|
|
|
938
|
-
|
|
963
|
+
instruct_trace["mode"] = mode
|
|
939
964
|
if mode_reason:
|
|
940
|
-
|
|
965
|
+
instruct_trace["mode_reason"] = mode_reason
|
|
941
966
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
return "", search_payloads,
|
|
967
|
+
instruct_trace["output"] = ""
|
|
968
|
+
instruct_trace["usage"] = usage
|
|
969
|
+
return "", search_payloads, instruct_trace, usage, search_time
|
|
945
970
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
return "", search_payloads,
|
|
971
|
+
instruct_trace["mode"] = mode
|
|
972
|
+
instruct_trace["output"] = (response.content or "").strip()
|
|
973
|
+
instruct_trace["usage"] = usage
|
|
974
|
+
return "", search_payloads, instruct_trace, usage, 0.0
|
|
950
975
|
|
|
951
976
|
def _format_search_msgs(self) -> str:
|
|
952
977
|
"""Format search snippets only (not crawled pages)."""
|
|
@@ -1026,9 +1051,9 @@ class ProcessingPipeline:
|
|
|
1026
1051
|
parts.append(fence("text", v.get("output", "")))
|
|
1027
1052
|
parts.append("")
|
|
1028
1053
|
|
|
1029
|
-
if trace.get("
|
|
1030
|
-
t = trace["
|
|
1031
|
-
parts.append("##
|
|
1054
|
+
if trace.get("instruct"):
|
|
1055
|
+
t = trace["instruct"]
|
|
1056
|
+
parts.append("## Instruct\n")
|
|
1032
1057
|
parts.append(f"- model: `{t.get('model')}`")
|
|
1033
1058
|
parts.append(f"- base_url: `{t.get('base_url')}`\n")
|
|
1034
1059
|
parts.append("### Prompt\n")
|
entari_plugin_hyw/core/render.py
CHANGED
|
@@ -441,6 +441,33 @@ class ContentRenderer:
|
|
|
441
441
|
**stage_children # Merge children
|
|
442
442
|
})
|
|
443
443
|
|
|
444
|
+
# Ensure references are displayed even if no "Search" stage was present
|
|
445
|
+
has_search_stage = any(s.get("name") == "Search" for s in processed_stages)
|
|
446
|
+
if not has_search_stage and (processed_refs or processed_image_refs):
|
|
447
|
+
# Create a virtual Search stage
|
|
448
|
+
virtual_search = {
|
|
449
|
+
"name": "Search",
|
|
450
|
+
"model": "DuckDuckGo", # Default assumption
|
|
451
|
+
"model_short": "DuckDuckGo",
|
|
452
|
+
"provider": "Reference",
|
|
453
|
+
"icon_html": SEARCH_ICON,
|
|
454
|
+
"time_str": "0.00s",
|
|
455
|
+
"cost_str": "$0",
|
|
456
|
+
}
|
|
457
|
+
if processed_refs:
|
|
458
|
+
virtual_search['references'] = processed_refs
|
|
459
|
+
if processed_image_refs:
|
|
460
|
+
virtual_search['image_references'] = processed_image_refs
|
|
461
|
+
|
|
462
|
+
# Insert after Vision/Instruct (usually index 0 or 1), or at start
|
|
463
|
+
insert_idx = 0
|
|
464
|
+
if processed_stages and processed_stages[0]["name"] in ["Vision", "Instruct"]:
|
|
465
|
+
insert_idx = 1
|
|
466
|
+
if len(processed_stages) > 1 and processed_stages[1]["name"] == "Instruct":
|
|
467
|
+
insert_idx = 2
|
|
468
|
+
|
|
469
|
+
processed_stages.insert(insert_idx, virtual_search)
|
|
470
|
+
|
|
444
471
|
# 4. Stats Footer Logic
|
|
445
472
|
processed_stats = {}
|
|
446
473
|
stats_dict = {}
|
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
VISION_SP = """#
|
|
1
|
+
VISION_SP = """# You are a professional vision-to-text expert.
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
3
|
+
# Core Tasks
|
|
4
|
+
- Intelligently analyze image content and paraphrase it into text. Do not add any other content.
|
|
5
|
+
- Text Priority: If there is clear text (documents, screenshots, etc.), it must be transcribed completely and accurately, without omission.
|
|
6
|
+
- Visual Supplement: After explaining the text, describe the visual content summary (objects, scenes, atmosphere).
|
|
7
|
+
- User Requirements: Focus on text transcription based on the hint in the user message, ignore if irrelevant.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## User Message
|
|
10
10
|
```text
|
|
11
11
|
{user_msgs}
|
|
12
12
|
```
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
##
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
>
|
|
29
|
-
|
|
30
|
-
##
|
|
31
|
-
-
|
|
15
|
+
INSTRUCT_SP = """# You are a professional instruction expert.
|
|
16
|
+
|
|
17
|
+
## Core Tasks
|
|
18
|
+
- Decide on preprocessing tools:
|
|
19
|
+
- User message contains a link: Call `crawl_page` to get content, no other tools needed.
|
|
20
|
+
- User message contains typical nouns or possible proper noun combinations: Call `internal_web_search`.
|
|
21
|
+
- Extract keywords to search for the keywords themselves, do not add any other particles, for best search results.
|
|
22
|
+
- If user message keywords are clear, and image search can find posters, landmarks, items, character drawings, etc., call `internal_image_search`.
|
|
23
|
+
- User message does not need search: Do not call tools.
|
|
24
|
+
- Call `set_mode`:
|
|
25
|
+
- Most routine questions: `standard`.
|
|
26
|
+
- User requests research / deep search: `agent`.
|
|
27
|
+
- Need to get specific page information to answer the question: `agent`.
|
|
28
|
+
> All tools need to be called simultaneously in this conversation.
|
|
29
|
+
|
|
30
|
+
## Call Tools
|
|
31
|
+
- When using tools, you must call them via the `function_call` / `tool_call` mechanism.
|
|
32
32
|
{tools_desc}
|
|
33
33
|
|
|
34
|
-
##
|
|
35
|
-
|
|
34
|
+
## Your Reply
|
|
35
|
+
Do not reply with extra text after calling tools to save tokens.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## User Message
|
|
38
38
|
```
|
|
39
39
|
{user_msgs}
|
|
40
40
|
```
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
##
|
|
44
|
+
INSTRUCT_SP_VISION_ADD = """
|
|
45
|
+
## Vision Expert Message
|
|
46
46
|
```text
|
|
47
47
|
{vision_msgs}
|
|
48
48
|
```
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
-
AGENT_SP = """#
|
|
52
|
-
>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
>
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
- search
|
|
76
|
-
- page
|
|
77
|
-
- image
|
|
78
|
-
-
|
|
79
|
-
-
|
|
51
|
+
AGENT_SP = """# You are an Agent Control Expert. You need to understand user intent and provide a final reply based on available information.
|
|
52
|
+
> Please ensure that any message you output has an accurate source to reduce misinformation.
|
|
53
|
+
|
|
54
|
+
Current Mode: {mode}, {mode_desc}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Process Requirements
|
|
59
|
+
When sending text without calling tools, it means this is the final reply. Please observe:
|
|
60
|
+
- Provide a report directly, no need to explicitly answer the user message.
|
|
61
|
+
- Language: {language}, encyclopedic style, rigorous and concise language.
|
|
62
|
+
- Body Format:
|
|
63
|
+
- Use Markdown format, supporting highlight, katex.
|
|
64
|
+
- Give a `# ` main title at the beginning, no extra nonsense, do not directly answer the user's question.
|
|
65
|
+
- Rich content highlighting key points.
|
|
66
|
+
- Tool Citation:
|
|
67
|
+
> Important: All body content must be based on actual information, ensuring 100% accuracy.
|
|
68
|
+
- Citation Rules:
|
|
69
|
+
- Cite information only if it is useful for solving the problem in this session; do not cite unnecessary messages.
|
|
70
|
+
- Badges must truly correspond to the information obtained in the context, and correspond to the content in references. Images correspond in order.
|
|
71
|
+
- Citation Rules in Body:
|
|
72
|
+
- Search Summary Citation: Use like `[search:3][search:4]`
|
|
73
|
+
- Page Content Citation: Use like `[page:5][page:6]`
|
|
74
|
+
- Image Citation: Use like `[image:7][image:8]`
|
|
75
|
+
- `search` means search summaries obtained using `internal_web_search`. Do not cite if no relevant info from this tool.
|
|
76
|
+
- `page` means page content obtained using `crawl_page`. Do not cite if no relevant info from this tool.
|
|
77
|
+
- `image` means images obtained using `internal_image_search`. Just place images in order, you do not need to explicitly cite.
|
|
78
|
+
- Add a `references` code block at the bottom of the body:
|
|
79
|
+
- Do not write unused entries. Do not write if experts gave no info.
|
|
80
80
|
```references
|
|
81
|
-
[2] [search] [
|
|
82
|
-
[8] [search] [
|
|
83
|
-
[1] [page] [
|
|
84
|
-
[2] [page] [
|
|
85
|
-
[1] [image] [
|
|
81
|
+
[2] [search] [Text Description](url)
|
|
82
|
+
[8] [search] [Text Description](url)
|
|
83
|
+
[1] [page] [Page Title](url)
|
|
84
|
+
[2] [page] [Page Title](url)
|
|
85
|
+
[1] [image] [Source](url)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
##
|
|
88
|
+
## User Message
|
|
89
89
|
```text
|
|
90
90
|
{user_msgs}
|
|
91
91
|
```
|
|
92
92
|
"""
|
|
93
93
|
|
|
94
94
|
AGENT_SP_TOOLS_STANDARD_ADD = """
|
|
95
|
-
|
|
95
|
+
You need to integrate existing information, extract keywords from the user message, and make a final reply.
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
AGENT_SP_TOOLS_AGENT_ADD = """
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
- 0-1
|
|
103
|
-
- 0-1
|
|
104
|
-
- 1-2
|
|
105
|
-
-
|
|
100
|
+
- You can now use tools: {tools_desc}
|
|
101
|
+
- You need to judge whether to use tools sequentially or concurrently to obtain information:
|
|
102
|
+
- 0-1 times `internal_web_search`
|
|
103
|
+
- 0-1 times `internal_image_search` (if user needs images, usually concurrent with `internal_web_search`)
|
|
104
|
+
- 1-2 times `crawl_page`
|
|
105
|
+
- When using tools, you must call them via the `function_call` / `tool_call` mechanism.
|
|
106
106
|
"""
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
##
|
|
110
|
+
AGENT_SP_INSTRUCT_VISION_ADD = """
|
|
111
|
+
## Vision Expert Message
|
|
112
112
|
```text
|
|
113
113
|
{vision_msgs}
|
|
114
114
|
```
|
|
115
115
|
"""
|
|
116
116
|
|
|
117
117
|
AGENT_SP_SEARCH_ADD = """
|
|
118
|
-
##
|
|
118
|
+
## Search Expert Message
|
|
119
119
|
```text
|
|
120
120
|
{search_msgs}
|
|
121
121
|
```
|
|
@@ -124,17 +124,17 @@ AGENT_SP_SEARCH_ADD = """
|
|
|
124
124
|
"""
|
|
125
125
|
|
|
126
126
|
AGENT_SP_PAGE_ADD = """
|
|
127
|
-
##
|
|
127
|
+
## Page Content Expert Message
|
|
128
128
|
```text
|
|
129
129
|
{page_msgs}
|
|
130
130
|
```
|
|
131
|
-
-
|
|
131
|
+
- When citing page content, you must use the `page:id` format.
|
|
132
132
|
"""
|
|
133
133
|
|
|
134
134
|
AGENT_SP_IMAGE_SEARCH_ADD = """
|
|
135
|
-
##
|
|
135
|
+
## Image Search Expert Message
|
|
136
136
|
```text
|
|
137
137
|
{image_search_msgs}
|
|
138
138
|
```
|
|
139
|
-
-
|
|
139
|
+
- For every `internal_image_search` performed, pick 1 image to insert into the body.
|
|
140
140
|
"""
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
VISION_SP = """# 你是一个专业的视觉转文字专家.
|
|
2
|
+
|
|
3
|
+
# 核心任务
|
|
4
|
+
- 智能分析图片内容, 转述成文本, 除此之外不要添加任何内容
|
|
5
|
+
- 文字优先: 若包含清晰文字(文档、截图等), 必须完整准确转录, 不要遗漏.
|
|
6
|
+
- 视觉补充: 解释完文字后, 描述视觉内容总结(物体、场景、氛围).
|
|
7
|
+
- 用户要求: 根据用户消息中提示侧重转文本的偏向, 若无关联则不理会.
|
|
8
|
+
|
|
9
|
+
## 用户消息
|
|
10
|
+
```text
|
|
11
|
+
{user_msgs}
|
|
12
|
+
```
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
INSTRUCT_SP = """# 你是一个专业的指导专家.
|
|
16
|
+
|
|
17
|
+
## 核心任务
|
|
18
|
+
- 决定预处理工具:
|
|
19
|
+
- 用户消息包含链接: 调用 crawl_page 获取内容, 无需其他工具
|
|
20
|
+
- 用户消息包含典型名词、可能的专有名词组合: 调用 internal_web_search
|
|
21
|
+
- 提炼出关键词搜索关键词本身, 不添加任何其他助词, 搜索效果最好
|
|
22
|
+
- 如果用户消息关键词清晰, 使用图片搜索能搜索出诸如海报、地标、物品、角色立绘等, 调用 internal_image_search
|
|
23
|
+
- 用户消息不需要搜索: 不调用工具
|
|
24
|
+
- 调用 set_mode:
|
|
25
|
+
- 绝大部分常规问题: standard
|
|
26
|
+
- 用户要求研究/深度搜索: agent
|
|
27
|
+
- 需要获取页面具体信息才能回答问题: agent
|
|
28
|
+
> 所有工具需要在本次对话同时调用
|
|
29
|
+
|
|
30
|
+
## 调用工具
|
|
31
|
+
- 使用工具时, 必须通过 function_call / tool_call 机制调用.
|
|
32
|
+
{tools_desc}
|
|
33
|
+
|
|
34
|
+
## 你的回复
|
|
35
|
+
调用工具后无需回复额外文本节省token.
|
|
36
|
+
|
|
37
|
+
## 用户消息
|
|
38
|
+
```
|
|
39
|
+
{user_msgs}
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
INSTRUCT_SP_VISION_ADD = """
|
|
45
|
+
## 视觉专家消息
|
|
46
|
+
```text
|
|
47
|
+
{vision_msgs}
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
AGENT_SP = """# 你是一个 Agent 总控专家, 你需要理解用户意图, 根据已有信息给出最终回复.
|
|
52
|
+
> 请确保你输出的任何消息有着准确的来源, 减少输出错误信息.
|
|
53
|
+
|
|
54
|
+
当前模式: {mode}, {mode_desc}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## 过程要求
|
|
59
|
+
当不调用工具发送文本, 即会变成最终回复, 请遵守:
|
|
60
|
+
- 直接给出一篇报告, 无需回答用户消息
|
|
61
|
+
- 语言: {language}, 百科式风格, 语言严谨不啰嗦.
|
|
62
|
+
- 正文格式:
|
|
63
|
+
- 使用 Markdown 格式, 支持 hightlight, katex
|
|
64
|
+
- 最开始给出`# `大标题, 不要有多余废话, 不要直接回答用户的提问.
|
|
65
|
+
- 内容丰富突出重点.
|
|
66
|
+
- 工具引用:
|
|
67
|
+
> 重要: 所有正文内容必须基于实际信息, 保证百分百真实度
|
|
68
|
+
- 引用规则:
|
|
69
|
+
- 本次会话中存在对解决此问题有用的信息才加以引用, 不需要的消息可以不引用.
|
|
70
|
+
- 角标必须真实对应上下文中获取的信息, 同时对应 references 中的内容, 图片按顺序对应.
|
|
71
|
+
- 正文中的引用规则
|
|
72
|
+
- 搜索摘要引用: 使用如 [search:3][search:4]
|
|
73
|
+
- 页面内容引用: 使用如 [page:5][page:6]
|
|
74
|
+
- 图片引用: 使用如 [image:7][image:8]
|
|
75
|
+
- search 的意思是你使用 internal_web_search 获取的搜索摘要, 如果没有此工具相关信息则不引用
|
|
76
|
+
- page 的意思是你使用 crawl_page 获取的页面内容, 如果没有此工具相关信息则不引用
|
|
77
|
+
- image 的意思是你使用 internal_image_search 获取的图片, 图片按顺序摆放即可, 你无需显式引用
|
|
78
|
+
- 在正文底部添加 references 代码块:
|
|
79
|
+
- 用不到的条目不写, 没有专家给信息就不写.
|
|
80
|
+
```references
|
|
81
|
+
[2] [search] [文本描述](url)
|
|
82
|
+
[8] [search] [文本描述](url)
|
|
83
|
+
[1] [page] [页面标题](url)
|
|
84
|
+
[2] [page] [页面标题](url)
|
|
85
|
+
[1] [image] [来源](url)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 用户消息
|
|
89
|
+
```text
|
|
90
|
+
{user_msgs}
|
|
91
|
+
```
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
AGENT_SP_TOOLS_STANDARD_ADD = """
|
|
95
|
+
你需要整合已有的信息, 提炼用户消息中的关键词, 进行最终回复.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
AGENT_SP_TOOLS_AGENT_ADD = """
|
|
100
|
+
- 你现在可以使用工具: {tools_desc}
|
|
101
|
+
- 你需要判断顺序或并发使用工具获取信息:
|
|
102
|
+
- 0-1 次 internal_web_search
|
|
103
|
+
- 0-1 次 internal_image_search (如果用户需要图片, 通常和 internal_web_search 并发执行)
|
|
104
|
+
- 1-2 次 crawl_page
|
|
105
|
+
- 使用工具时, 必须通过 function_call / tool_call 机制调用.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
AGENT_SP_INSTRUCT_VISION_ADD = """
|
|
111
|
+
## 视觉专家消息
|
|
112
|
+
```text
|
|
113
|
+
{vision_msgs}
|
|
114
|
+
```
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
AGENT_SP_SEARCH_ADD = """
|
|
118
|
+
## 搜索专家消息
|
|
119
|
+
```text
|
|
120
|
+
{search_msgs}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
AGENT_SP_PAGE_ADD = """
|
|
127
|
+
## 页面内容专家消息
|
|
128
|
+
```text
|
|
129
|
+
{page_msgs}
|
|
130
|
+
```
|
|
131
|
+
- 引用页面内容时, 必须使用 `page:id` 格式
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
AGENT_SP_IMAGE_SEARCH_ADD = """
|
|
135
|
+
## 图像搜索专家消息
|
|
136
|
+
```text
|
|
137
|
+
{image_search_msgs}
|
|
138
|
+
```
|
|
139
|
+
- 每进行一次 internal_image_search, 挑选 1 张图像插入正文
|
|
140
|
+
"""
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: entari_plugin_hyw
|
|
3
|
+
Version: 3.3.8
|
|
4
|
+
Summary: Use large language models to interpret chat messages
|
|
5
|
+
Author-email: kumoSleeping <zjr2992@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kumoSleeping/entari-plugin-hyw
|
|
8
|
+
Project-URL: Repository, https://github.com/kumoSleeping/entari-plugin-hyw
|
|
9
|
+
Project-URL: Issue Tracker, https://github.com/kumoSleeping/entari-plugin-hyw/issues
|
|
10
|
+
Keywords: entari,llm,ai,bot,chat
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: arclet-entari[full]>=0.16.5
|
|
20
|
+
Requires-Dist: openai
|
|
21
|
+
Requires-Dist: httpx
|
|
22
|
+
Requires-Dist: markdown>=3.10
|
|
23
|
+
Requires-Dist: crawl4ai>=0.7.8
|
|
24
|
+
Requires-Dist: jinja2>=3.0
|
|
25
|
+
Requires-Dist: ddgs>=9.10.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: entari-plugin-server>=0.5.0; extra == "dev"
|
|
28
|
+
Requires-Dist: satori-python-adapter-onebot11>=0.2.5; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# Entari Plugin HYW
|
|
31
|
+
|
|
32
|
+
[](https://badge.fury.io/py/entari-plugin-hyw)
|
|
33
|
+
[](https://opensource.org/licenses/MIT)
|
|
34
|
+
[](https://pypi.org/project/entari-plugin-hyw/)
|
|
35
|
+
|
|
36
|
+
**English** | [简体中文](docs/README_CN.md)
|
|
37
|
+
|
|
38
|
+
**Entari Plugin HYW** is an advanced agentic chat plugin for the [Entari](https://github.com/entari-org/entari) framework. It leverages Large Language Models (LLMs) to provide intelligent, context-aware, and multi-modal responses within instant messaging environments (OneBot 11, Satori).
|
|
39
|
+
|
|
40
|
+
The plugin implements a three-stage pipeline (**Vision**, **Instruct**, **Agent**) to autonomously decide when to search the web, crawl pages, or analyze images to answer user queries effectively.
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<img src="docs/demo_mockup.svg" width="800" />
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- 📖 **Agentic Workflow**
|
|
49
|
+
Autonomous decision-making process to search, browse, and reason.
|
|
50
|
+
|
|
51
|
+
- 🎑 **Multi-Modal Support**
|
|
52
|
+
Native support for image analysis using Vision Language Models (VLMs).
|
|
53
|
+
|
|
54
|
+
- 🔍 **Web Search & Crawling**
|
|
55
|
+
Integrated **DuckDuckGo** and **Crawl4AI** for real-time information retrieval.
|
|
56
|
+
|
|
57
|
+
- 🎨 **Rich Rendering**
|
|
58
|
+
Responses are rendered as images containing Markdown, syntax-highlighted code, LaTeX math, and citation badges.
|
|
59
|
+
|
|
60
|
+
- 🔌 **Protocol Support**
|
|
61
|
+
Deep integration with OneBot 11 and Satori protocols, handling reply context and JSON cards perfectly.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install entari-plugin-hyw
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Configure the plugin in your `entari.yml`.
|
|
72
|
+
|
|
73
|
+
### Minimal Configuration
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
plugins:
|
|
77
|
+
entari_plugin_hyw:
|
|
78
|
+
model_name: google/gemini-3-flash-preview
|
|
79
|
+
api_key: "your-or-api-key-here"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
### Commands
|
|
85
|
+
|
|
86
|
+
- **Text Query**
|
|
87
|
+
```text
|
|
88
|
+
/q What's the latest news on Rust 1.83?
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- **Image Analysis**
|
|
92
|
+
*(Send an image with command, or reply to an image)*
|
|
93
|
+
```text
|
|
94
|
+
/q [Image] Explain this error.
|
|
95
|
+
```
|
|
96
|
+
- **Quote Query**
|
|
97
|
+
```text
|
|
98
|
+
[quote: User Message] /q
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- **Follow-up**
|
|
102
|
+
*Reply to the bot's message to continue the conversation.*
|
|
103
|
+
|
|
104
|
+
## Documentation for AI/LLMs
|
|
105
|
+
|
|
106
|
+
- [Instruction Guide (English)](docs/README_LLM_EN.md)
|
|
107
|
+
- [指导手册 (简体中文)](docs/README_LLM_CN.md)
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
This project is licensed under the MIT License.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
entari_plugin_hyw/__init__.py,sha256=
|
|
1
|
+
entari_plugin_hyw/__init__.py,sha256=BwlY42IJj51Oo8hHrTxbCDItRA3bMr7Z1G-3HUEyWhQ,19708
|
|
2
2
|
entari_plugin_hyw/assets/package-lock.json,sha256=TIrLM-wLWZTrp3LKfzhEVuduhvBJmI93NdQEKYLW2W0,33172
|
|
3
3
|
entari_plugin_hyw/assets/package.json,sha256=Y4H8JGtp3nv2WUtI20tXoXWddR-dwwKJhqQVLercpiw,306
|
|
4
4
|
entari_plugin_hyw/assets/tailwind.config.js,sha256=S8I9X8hI8IaQRczWK9hTW-zl4oVpAXw5ykeksrzHjpU,382
|
|
@@ -11,6 +11,7 @@ entari_plugin_hyw/assets/icon/deepseek.png,sha256=KWWAr9aeYMc6I07U_1qo7zcXO6e7-k
|
|
|
11
11
|
entari_plugin_hyw/assets/icon/gemini.svg,sha256=H74CoVmx5opcCtr3Ay3M09dpqL9cd9Whkx-M6an3t7s,599
|
|
12
12
|
entari_plugin_hyw/assets/icon/google.svg,sha256=H74CoVmx5opcCtr3Ay3M09dpqL9cd9Whkx-M6an3t7s,599
|
|
13
13
|
entari_plugin_hyw/assets/icon/grok.png,sha256=uSulvvDVqoA4RUOW0ZAkdvBVM2rpyGJRZIbn5dEFspw,362
|
|
14
|
+
entari_plugin_hyw/assets/icon/huggingface.png,sha256=8eAudeftUDO11jf0coOscPeRkskCb7l9TNMx78q61mY,24564
|
|
14
15
|
entari_plugin_hyw/assets/icon/microsoft.svg,sha256=-am_6N3UEQYSzldDg-xrdGYjTWsagH-3v4Q_eia1ymE,684
|
|
15
16
|
entari_plugin_hyw/assets/icon/minimax.png,sha256=tWqVlMdFNPpP8zWWX9tvIsWXI9q76P7O3t3CEZO7NU0,1525
|
|
16
17
|
entari_plugin_hyw/assets/icon/mistral.png,sha256=0vv7jPmPKiBRYVYYJxVL_wIH_qa_ZssIdV3NDO5vbmk,869
|
|
@@ -20,6 +21,7 @@ entari_plugin_hyw/assets/icon/openrouter.png,sha256=exxfjWGDWpYH-Vc8xJDbhNVeXFEV
|
|
|
20
21
|
entari_plugin_hyw/assets/icon/perplexity.svg,sha256=mHWZFoeWmDYXOIDzm9pj6_sRotaI8xNy5Lkeg5Vzu70,555
|
|
21
22
|
entari_plugin_hyw/assets/icon/qwen.png,sha256=eqLbnIPbjh2_PsODU_mmqjeD82xXj8fV_kN0fDrNaD0,38419
|
|
22
23
|
entari_plugin_hyw/assets/icon/xai.png,sha256=uSulvvDVqoA4RUOW0ZAkdvBVM2rpyGJRZIbn5dEFspw,362
|
|
24
|
+
entari_plugin_hyw/assets/icon/xiaomi.png,sha256=WHxlDFGU5FCjb-ure3ngdGG18-efYZUUfqA3_lqCUN0,4084
|
|
23
25
|
entari_plugin_hyw/assets/icon/zai.png,sha256=K-gnabdsjMLInppHA1Op7Nyt33iegrx1x-yNlvCZ0Tc,2351
|
|
24
26
|
entari_plugin_hyw/assets/libs/highlight.css,sha256=Oppd74ucMR5a5Dq96FxjEzGF7tTw2fZ_6ksAqDCM8GY,1309
|
|
25
27
|
entari_plugin_hyw/assets/libs/highlight.js,sha256=g3pvpbDHNrUrveKythkPMF2j_J7UFoHbUyFQcFe1yEY,121727
|
|
@@ -28,18 +30,19 @@ entari_plugin_hyw/assets/libs/katex.css,sha256=UF1fgpAiu3tPJN_uCqEUHNe7pnr-QR0SQ
|
|
|
28
30
|
entari_plugin_hyw/assets/libs/katex.js,sha256=3ISyluw-iE3gkxWPdg_Z1Ftser5YtTgVV_ThOPRqWK4,277038
|
|
29
31
|
entari_plugin_hyw/assets/libs/tailwind.css,sha256=ee_3txpnxhChZOjSJQUX0XiL1Nq0U2KLTvSGJLZBlaA,19916
|
|
30
32
|
entari_plugin_hyw/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
entari_plugin_hyw/core/config.py,sha256=
|
|
33
|
+
entari_plugin_hyw/core/config.py,sha256=uKZOuK9bG1W-E5UzhKX-EcYu2nFdxfl9EEaSwVgFtK4,1653
|
|
32
34
|
entari_plugin_hyw/core/history.py,sha256=vqp7itwR5-KaqC4Ftmq6GOz7OM9GsiFJnSN9JJ2P6L4,5894
|
|
33
35
|
entari_plugin_hyw/core/hyw.py,sha256=RCRjV9uYmvXysiliztphLP3VyUabrf0LY2Bk66W5JGA,1927
|
|
34
|
-
entari_plugin_hyw/core/pipeline.py,sha256=
|
|
35
|
-
entari_plugin_hyw/core/render.py,sha256=
|
|
36
|
+
entari_plugin_hyw/core/pipeline.py,sha256=6fA59ObZSV7Cb0mluOumpXve0m0WFGFgAopHVRgbah4,49197
|
|
37
|
+
entari_plugin_hyw/core/render.py,sha256=rUhv2R5fdtsMIGg-Q1qe8hhUWC1_E50BODLA78u4_SI,28948
|
|
36
38
|
entari_plugin_hyw/utils/__init__.py,sha256=TnkxDqYr0zgRE7TC92tVbUaY8m1UyyoLg2zvzQ8nMVI,84
|
|
37
39
|
entari_plugin_hyw/utils/browser.py,sha256=LJlFh-oSqt9mQBpMALxbYGUG__t1YLUo7RxUAslsWUc,1416
|
|
38
40
|
entari_plugin_hyw/utils/misc.py,sha256=_7iHVYj_mJ6OGq6FU1s_cFeS1Ao-neBjZYd6eI2p95U,3482
|
|
39
41
|
entari_plugin_hyw/utils/playwright_tool.py,sha256=ZZNkzFtUt_Gxny3Od4boBAgNF9J0N84uySatzn1Bwe4,1272
|
|
40
|
-
entari_plugin_hyw/utils/prompts.py,sha256=
|
|
42
|
+
entari_plugin_hyw/utils/prompts.py,sha256=eybCtSuW4F13jQtleHXF6CQypCIIutDT7mpbkXS48Gs,4993
|
|
43
|
+
entari_plugin_hyw/utils/prompts_cn.py,sha256=87ti20ofjc8QW3i8HaPCUpDfl0EsS-ynz78e4tCz4Cg,4431
|
|
41
44
|
entari_plugin_hyw/utils/search.py,sha256=Bvz2KFw3Gr2nuvmlo_8ExLHvO353NKX-YN35A2FCsBw,19047
|
|
42
|
-
entari_plugin_hyw-3.3.
|
|
43
|
-
entari_plugin_hyw-3.3.
|
|
44
|
-
entari_plugin_hyw-3.3.
|
|
45
|
-
entari_plugin_hyw-3.3.
|
|
45
|
+
entari_plugin_hyw-3.3.8.dist-info/METADATA,sha256=a6rO4NRoNrbOG3MjRbFeCxCuYkm8OoIYpAaibPxszgM,3598
|
|
46
|
+
entari_plugin_hyw-3.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
47
|
+
entari_plugin_hyw-3.3.8.dist-info/top_level.txt,sha256=TIDsn6XPs6KA5e3ezsE65JoXsy03ejDdrB41I4SPjmo,18
|
|
48
|
+
entari_plugin_hyw-3.3.8.dist-info/RECORD,,
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: entari_plugin_hyw
|
|
3
|
-
Version: 3.3.6
|
|
4
|
-
Summary: Use large language models to interpret chat messages
|
|
5
|
-
Author-email: kumoSleeping <zjr2992@outlook.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/kumoSleeping/entari-plugin-hyw
|
|
8
|
-
Project-URL: Repository, https://github.com/kumoSleeping/entari-plugin-hyw
|
|
9
|
-
Project-URL: Issue Tracker, https://github.com/kumoSleeping/entari-plugin-hyw/issues
|
|
10
|
-
Keywords: entari,llm,ai,bot,chat
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Python: >=3.10
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: arclet-entari[full]>=0.16.5
|
|
20
|
-
Requires-Dist: openai
|
|
21
|
-
Requires-Dist: httpx
|
|
22
|
-
Requires-Dist: markdown>=3.10
|
|
23
|
-
Requires-Dist: crawl4ai>=0.7.8
|
|
24
|
-
Requires-Dist: jinja2>=3.0
|
|
25
|
-
Requires-Dist: ddgs>=9.10.0
|
|
26
|
-
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: entari-plugin-server>=0.5.0; extra == "dev"
|
|
28
|
-
Requires-Dist: satori-python-adapter-onebot11>=0.2.5; extra == "dev"
|
|
29
|
-
|
|
30
|
-
<div align="center">
|
|
31
|
-
|
|
32
|
-
# Entari Plugin HYW
|
|
33
|
-
|
|
34
|
-
**Entari 智能聊天解释插件**
|
|
35
|
-
|
|
36
|
-
[](https://opensource.org/licenses/MIT) [](https://pypi.org/project/entari-plugin-hyw/) [](https://www.python.org/downloads/)
|
|
37
|
-
|
|
38
|
-
*IM 环境下的 LLM 智能解释方案*
|
|
39
|
-
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
# v3.3 迎来大幅度改动、现在图文不符
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
## 🎑 效果展示
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<div align="center">
|
|
51
|
-
<img src="demo.svg" alt="Chat Demo" width="100%">
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
## ✨ 功能特性
|
|
55
|
-
- **关于搜索**:一次性触发 Bing 网页与图片搜索,组合结果后再回应。
|
|
56
|
-
- 给予 `Alconna` 与 `MessageChain` 混合处理, 深度优化触发体验。
|
|
57
|
-
- **网页获取**:使用 Playwright 进行实时页面获取。
|
|
58
|
-
- **多模态理解**:支持图片视觉分析。
|
|
59
|
-
- **上下文感知**:维护对话历史记录,支持连续的多轮对话。
|
|
60
|
-
- `reaction` 表情, 表示任务开始。
|
|
61
|
-
- **OneBot 优化**:针对 OneBot 11 协议深度优化,支持解析 JSON 卡片、引用消息等特殊元素。
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## 📦 安装
|
|
66
|
-
|
|
67
|
-
### 基础安装
|
|
68
|
-
```bash
|
|
69
|
-
pip install entari-plugin-hyw
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 搜索
|
|
73
|
-
默认通过 HTTP 请求搜索引擎(DuckDuckGo,可在配置中自定义完整搜索链接,如 `https://duckduckgo.com/?q={query}`)。
|
|
74
|
-
|
|
75
|
-
## ⚙️ 配置
|
|
76
|
-
|
|
77
|
-
请在 `entari.yml` 中添加以下配置:
|
|
78
|
-
|
|
79
|
-
```yaml
|
|
80
|
-
plugins:
|
|
81
|
-
entari_plugin_hyw:
|
|
82
|
-
# --- 基础设置 ---
|
|
83
|
-
# 触发机器人的命令列表
|
|
84
|
-
command_name_list: ["zssm", "hyw"]
|
|
85
|
-
|
|
86
|
-
# 主 LLM 模型配置(必需), 如 x-ai/grok-4.1-fast:online、perplexity/sonar
|
|
87
|
-
model_name: "gx-ai/grok-4.1-fast:free"
|
|
88
|
-
api_key: "your-api-key"
|
|
89
|
-
|
|
90
|
-
# 默认 https://openrouter.ai/api/v1
|
|
91
|
-
base_url: "openai-compatible-url"
|
|
92
|
-
|
|
93
|
-
# --- 浏览器与搜索 ---
|
|
94
|
-
headless: true
|
|
95
|
-
|
|
96
|
-
# --- 视觉配置 (可选) ---
|
|
97
|
-
# 如果未设置,将回退使用主模型
|
|
98
|
-
vision_model_name: "qwen-vl-plus"
|
|
99
|
-
vision_api_key: "your-vision-api-key"
|
|
100
|
-
vision_base_url: "your-vision_base_url"
|
|
101
|
-
|
|
102
|
-
# --- openai extra_body ---
|
|
103
|
-
extra_body:
|
|
104
|
-
reasoning:
|
|
105
|
-
effort: low
|
|
106
|
-
|
|
107
|
-
# --- 交互体验 ---
|
|
108
|
-
# 是否开启表情反应 (默认: true)
|
|
109
|
-
reaction: true
|
|
110
|
-
|
|
111
|
-
# --- 调试 ---
|
|
112
|
-
save_conversation: false
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## 📖 使用方法
|
|
116
|
-
|
|
117
|
-
### 基础指令
|
|
118
|
-
使用配置的命令前缀与机器人交互:
|
|
119
|
-
|
|
120
|
-
```text
|
|
121
|
-
hyw 最近LLM有啥新闻, 是不是claude又被秒了
|
|
122
|
-
hyw [图片消息] 里面这人写代码怎么我一句都看不懂
|
|
123
|
-
hyw https://koishi.chat/ 怎么安装
|
|
124
|
-
[回复消息] hyw
|
|
125
|
-
[回复消息<[图片消息]>] hyw -t
|
|
126
|
-
[回复消息] hyw 补充: 这个rf的意思是github用户RF-Tar-Railt
|
|
127
|
-
[回复消息(hyw插件的输出)] /1 详细点描述
|
|
128
|
-
[回复消息(hyw插件的输出>] /那谁有多余解释器?
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### 选项参数
|
|
132
|
-
- `-t` / `--text`: 强制纯文本模式(跳过图片分析,节省 Token 或时间)。
|
|
133
|
-
|
|
134
|
-
```text
|
|
135
|
-
hyw -t 一大段话。
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 引用回复
|
|
139
|
-
支持引用消息进行追问,机器人会自动读取被引用的消息作为上下文:
|
|
140
|
-
- **引用 + 命令**:机器人将理解被引用消息的内容(包括图片)通过 `MessageChain` 操作拼接 `Text`、`Image` 与部分 `Custom`。
|
|
141
|
-
|
|
142
|
-
UncleCode. (2024). Crawl4AI: Open-source LLM Friendly Web Crawler & Scraper [Computer software].
|
|
143
|
-
GitHub. https://github.com/unclecode/crawl4ai
|
|
File without changes
|
|
File without changes
|