entari-plugin-hyw 3.3.7__tar.gz → 3.3.8__tar.gz
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-3.3.8/PKG-INFO +113 -0
- entari_plugin_hyw-3.3.8/README.md +84 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/pyproject.toml +1 -1
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/__init__.py +10 -10
- entari_plugin_hyw-3.3.8/src/entari_plugin_hyw/assets/icon/huggingface.png +0 -0
- entari_plugin_hyw-3.3.8/src/entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/config.py +8 -8
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/pipeline.py +57 -56
- entari_plugin_hyw-3.3.8/src/entari_plugin_hyw/utils/prompts.py +140 -0
- entari_plugin_hyw-3.3.7/src/entari_plugin_hyw/utils/prompts.py → entari_plugin_hyw-3.3.8/src/entari_plugin_hyw/utils/prompts_cn.py +4 -4
- entari_plugin_hyw-3.3.8/src/entari_plugin_hyw.egg-info/PKG-INFO +113 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw.egg-info/SOURCES.txt +3 -0
- entari_plugin_hyw-3.3.7/PKG-INFO +0 -142
- entari_plugin_hyw-3.3.7/README.md +0 -113
- entari_plugin_hyw-3.3.7/src/entari_plugin_hyw.egg-info/PKG-INFO +0 -142
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/MANIFEST.in +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/setup.cfg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/anthropic.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/deepseek.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/gemini.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/google.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/grok.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/microsoft.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/minimax.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/mistral.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/nvida.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/openai.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/openrouter.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/perplexity.svg +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/qwen.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/xai.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/icon/zai.png +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/highlight.css +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/highlight.js +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/katex-auto-render.js +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/katex.css +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/katex.js +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/libs/tailwind.css +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/package-lock.json +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/package.json +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/tailwind.config.js +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/tailwind.input.css +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/template.html +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/template.html.bak +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/assets/template.j2 +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/__init__.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/history.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/hyw.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/core/render.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/utils/__init__.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/utils/browser.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/utils/misc.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/utils/playwright_tool.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw/utils/search.py +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw.egg-info/dependency_links.txt +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw.egg-info/requires.txt +0 -0
- {entari_plugin_hyw-3.3.7 → entari_plugin_hyw-3.3.8}/src/entari_plugin_hyw.egg-info/top_level.txt +0 -0
|
@@ -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.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Entari Plugin HYW
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/entari-plugin-hyw)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://pypi.org/project/entari-plugin-hyw/)
|
|
6
|
+
|
|
7
|
+
**English** | [简体中文](docs/README_CN.md)
|
|
8
|
+
|
|
9
|
+
**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).
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="docs/demo_mockup.svg" width="800" />
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- 📖 **Agentic Workflow**
|
|
20
|
+
Autonomous decision-making process to search, browse, and reason.
|
|
21
|
+
|
|
22
|
+
- 🎑 **Multi-Modal Support**
|
|
23
|
+
Native support for image analysis using Vision Language Models (VLMs).
|
|
24
|
+
|
|
25
|
+
- 🔍 **Web Search & Crawling**
|
|
26
|
+
Integrated **DuckDuckGo** and **Crawl4AI** for real-time information retrieval.
|
|
27
|
+
|
|
28
|
+
- 🎨 **Rich Rendering**
|
|
29
|
+
Responses are rendered as images containing Markdown, syntax-highlighted code, LaTeX math, and citation badges.
|
|
30
|
+
|
|
31
|
+
- 🔌 **Protocol Support**
|
|
32
|
+
Deep integration with OneBot 11 and Satori protocols, handling reply context and JSON cards perfectly.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install entari-plugin-hyw
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
Configure the plugin in your `entari.yml`.
|
|
43
|
+
|
|
44
|
+
### Minimal Configuration
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
plugins:
|
|
48
|
+
entari_plugin_hyw:
|
|
49
|
+
model_name: google/gemini-3-flash-preview
|
|
50
|
+
api_key: "your-or-api-key-here"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### Commands
|
|
56
|
+
|
|
57
|
+
- **Text Query**
|
|
58
|
+
```text
|
|
59
|
+
/q What's the latest news on Rust 1.83?
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Image Analysis**
|
|
63
|
+
*(Send an image with command, or reply to an image)*
|
|
64
|
+
```text
|
|
65
|
+
/q [Image] Explain this error.
|
|
66
|
+
```
|
|
67
|
+
- **Quote Query**
|
|
68
|
+
```text
|
|
69
|
+
[quote: User Message] /q
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **Follow-up**
|
|
73
|
+
*Reply to the bot's message to continue the conversation.*
|
|
74
|
+
|
|
75
|
+
## Documentation for AI/LLMs
|
|
76
|
+
|
|
77
|
+
- [Instruction Guide (English)](docs/README_LLM_EN.md)
|
|
78
|
+
- [指导手册 (简体中文)](docs/README_LLM_CN.md)
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
This project is licensed under the MIT License.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "entari_plugin_hyw"
|
|
7
|
-
version = "3.3.
|
|
7
|
+
version = "3.3.8"
|
|
8
8
|
description = "Use large language models to interpret chat messages"
|
|
9
9
|
authors = [{name = "kumoSleeping", email = "zjr2992@outlook.com"}]
|
|
10
10
|
dependencies = [
|
|
@@ -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
|
|
@@ -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 = []
|
|
@@ -828,10 +827,10 @@ class ProcessingPipeline:
|
|
|
828
827
|
return f"Unknown tool {name}"
|
|
829
828
|
|
|
830
829
|
|
|
831
|
-
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):
|
|
832
831
|
try:
|
|
833
832
|
return await asyncio.wait_for(
|
|
834
|
-
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),
|
|
835
834
|
timeout=120.0,
|
|
836
835
|
)
|
|
837
836
|
except asyncio.TimeoutError:
|
|
@@ -841,7 +840,7 @@ class ProcessingPipeline:
|
|
|
841
840
|
logger.error(f"LLM Call Failed: {e}")
|
|
842
841
|
return type("obj", (object,), {"content": f"Error: Model failure ({e})", "tool_calls": None})(), {"input_tokens": 0, "output_tokens": 0}
|
|
843
842
|
|
|
844
|
-
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):
|
|
845
844
|
try:
|
|
846
845
|
payload_debug = json.dumps(messages)
|
|
847
846
|
logger.info(f"LLM Request Payload Size: {len(payload_debug)} chars")
|
|
@@ -856,6 +855,7 @@ class ProcessingPipeline:
|
|
|
856
855
|
tools=tools,
|
|
857
856
|
tool_choice=tool_choice,
|
|
858
857
|
temperature=self.config.temperature,
|
|
858
|
+
extra_body=extra_body,
|
|
859
859
|
)
|
|
860
860
|
logger.info(f"LLM Request RECEIVED after {time.time() - t0:.2f}s")
|
|
861
861
|
|
|
@@ -880,6 +880,7 @@ class ProcessingPipeline:
|
|
|
880
880
|
messages=[{"role": "system", "content": prompt}, {"role": "user", "content": content_payload}],
|
|
881
881
|
model=model,
|
|
882
882
|
client=client,
|
|
883
|
+
extra_body=getattr(self.config, "vision_extra_body", None),
|
|
883
884
|
)
|
|
884
885
|
return (response.content or "").strip(), usage
|
|
885
886
|
|
|
@@ -891,15 +892,14 @@ class ProcessingPipeline:
|
|
|
891
892
|
tools = [self.web_search_tool, self.image_search_tool, self.set_mode_tool, self.crawl_page_tool]
|
|
892
893
|
tools_desc = "- internal_web_search: 搜索文本\n- internal_image_search: 搜索图片\n- crawl_page: 获取网页内容\n- set_mode: 设定standard/agent模式"
|
|
893
894
|
|
|
894
|
-
|
|
895
|
-
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)
|
|
896
896
|
|
|
897
897
|
if vision_text:
|
|
898
|
-
prompt = f"{prompt}\\n\\n{
|
|
898
|
+
prompt = f"{prompt}\\n\\n{INSTRUCT_SP_VISION_ADD.format(vision_msgs=vision_text)}"
|
|
899
899
|
|
|
900
900
|
client = self._client_for(
|
|
901
|
-
api_key=getattr(self.config, "
|
|
902
|
-
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),
|
|
903
903
|
)
|
|
904
904
|
|
|
905
905
|
history: List[Dict[str, Any]] = [
|
|
@@ -913,12 +913,13 @@ class ProcessingPipeline:
|
|
|
913
913
|
tools=tools,
|
|
914
914
|
tool_choice="auto",
|
|
915
915
|
client=client,
|
|
916
|
+
extra_body=getattr(self.config, "instruct_extra_body", None),
|
|
916
917
|
)
|
|
917
918
|
|
|
918
919
|
search_payloads: List[str] = []
|
|
919
|
-
|
|
920
|
+
instruct_trace: Dict[str, Any] = {
|
|
920
921
|
"model": model,
|
|
921
|
-
"base_url": getattr(self.config, "
|
|
922
|
+
"base_url": getattr(self.config, "instruct_base_url", None) or self.config.base_url,
|
|
922
923
|
"prompt": prompt,
|
|
923
924
|
"user_input": user_input or "",
|
|
924
925
|
"vision_add": vision_text or "",
|
|
@@ -946,8 +947,8 @@ class ProcessingPipeline:
|
|
|
946
947
|
history.append(
|
|
947
948
|
{"tool_call_id": tc.id, "role": "tool", "name": tc.function.name, "content": str(result)}
|
|
948
949
|
)
|
|
949
|
-
|
|
950
|
-
|
|
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)})
|
|
951
952
|
|
|
952
953
|
if tc.function.name in ["web_search", "internal_web_search"]:
|
|
953
954
|
search_payloads.append(str(result))
|
|
@@ -959,18 +960,18 @@ class ProcessingPipeline:
|
|
|
959
960
|
mode = args.get("mode", mode)
|
|
960
961
|
mode_reason = args.get("reason", "")
|
|
961
962
|
|
|
962
|
-
|
|
963
|
+
instruct_trace["mode"] = mode
|
|
963
964
|
if mode_reason:
|
|
964
|
-
|
|
965
|
+
instruct_trace["mode_reason"] = mode_reason
|
|
965
966
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
return "", search_payloads,
|
|
967
|
+
instruct_trace["output"] = ""
|
|
968
|
+
instruct_trace["usage"] = usage
|
|
969
|
+
return "", search_payloads, instruct_trace, usage, search_time
|
|
969
970
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
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
|
|
974
975
|
|
|
975
976
|
def _format_search_msgs(self) -> str:
|
|
976
977
|
"""Format search snippets only (not crawled pages)."""
|
|
@@ -1050,9 +1051,9 @@ class ProcessingPipeline:
|
|
|
1050
1051
|
parts.append(fence("text", v.get("output", "")))
|
|
1051
1052
|
parts.append("")
|
|
1052
1053
|
|
|
1053
|
-
if trace.get("
|
|
1054
|
-
t = trace["
|
|
1055
|
-
parts.append("##
|
|
1054
|
+
if trace.get("instruct"):
|
|
1055
|
+
t = trace["instruct"]
|
|
1056
|
+
parts.append("## Instruct\n")
|
|
1056
1057
|
parts.append(f"- model: `{t.get('model')}`")
|
|
1057
1058
|
parts.append(f"- base_url: `{t.get('base_url')}`\n")
|
|
1058
1059
|
parts.append("### Prompt\n")
|