entari-plugin-hyw 3.3.2__tar.gz → 3.5.0rc1__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.5.0rc1/PKG-INFO +116 -0
- entari_plugin_hyw-3.5.0rc1/README.md +87 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/pyproject.toml +2 -1
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/__init__.py +406 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/index.html +135 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos/cerebras.svg +9 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos/huggingface.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos/xiaomi.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/vite.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/anthropic.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/cerebras.svg +9 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/deepseek.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/gemini.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/google.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/grok.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/huggingface.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/microsoft.svg +15 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/minimax.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/mistral.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/nvida.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/openai.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/openrouter.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/perplexity.svg +24 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/qwen.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/xai.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/xiaomi.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/icon/zai.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/.gitignore +24 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/README.md +5 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/index.html +16 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/package-lock.json +2342 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/package.json +31 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/anthropic.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/cerebras.svg +9 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/deepseek.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/gemini.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/google.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/grok.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/huggingface.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/microsoft.svg +15 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/minimax.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/mistral.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/nvida.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/openai.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/openrouter.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/perplexity.svg +24 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/qwen.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/xai.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/xiaomi.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/logos/zai.png +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/public/vite.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/App.vue +216 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/assets/vue.svg +1 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/components/HelloWorld.vue +41 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/components/MarkdownContent.vue +330 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/components/SectionCard.vue +41 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/components/StageCard.vue +163 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/main.ts +5 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/style.css +8 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/src/types.ts +51 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/tsconfig.app.json +16 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/tsconfig.json +7 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/tsconfig.node.json +26 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/card-ui/vite.config.ts +16 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/core/config.py +11 -11
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/core/hyw.py +22 -15
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/core/pipeline.py +249 -183
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/core/render_vue.py +255 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/test_output/render_0.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/test_output/render_1.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/test_output/render_2.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/test_output/render_3.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/test_output/render_4.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/tests/ui_test_output.jpg +0 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/tests/verify_ui.py +139 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/utils/misc.py +0 -3
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/utils/prompts.py +24 -32
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/utils/search.py +479 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw.egg-info/PKG-INFO +116 -0
- entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw.egg-info/SOURCES.txt +103 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw.egg-info/requires.txt +1 -0
- entari_plugin_hyw-3.3.2/PKG-INFO +0 -142
- entari_plugin_hyw-3.3.2/README.md +0 -114
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/__init__.py +0 -818
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/highlight.css +0 -10
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/highlight.js +0 -1213
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/katex-auto-render.js +0 -1
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/katex.css +0 -1
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/katex.js +0 -1
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/libs/tailwind.css +0 -1
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/package-lock.json +0 -953
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/package.json +0 -16
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/tailwind.config.js +0 -12
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/tailwind.input.css +0 -235
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/template.html +0 -157
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/template.html.bak +0 -157
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/template.j2 +0 -307
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/core/render.py +0 -596
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/core/render.py.bak +0 -926
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/utils/search.py +0 -241
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw.egg-info/PKG-INFO +0 -142
- entari_plugin_hyw-3.3.2/src/entari_plugin_hyw.egg-info/SOURCES.txt +0 -50
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/MANIFEST.in +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/setup.cfg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/anthropic.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/deepseek.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/gemini.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/google.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/grok.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/microsoft.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/minimax.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/mistral.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/nvida.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/openai.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/openrouter.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/perplexity.svg +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/qwen.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/xai.png +0 -0
- {entari_plugin_hyw-3.3.2/src/entari_plugin_hyw/assets/icon → entari_plugin_hyw-3.5.0rc1/src/entari_plugin_hyw/assets/card-dist/logos}/zai.png +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/core/__init__.py +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/core/history.py +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/utils/__init__.py +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/utils/browser.py +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw/utils/playwright_tool.py +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw.egg-info/dependency_links.txt +0 -0
- {entari_plugin_hyw-3.3.2 → entari_plugin_hyw-3.5.0rc1}/src/entari_plugin_hyw.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: entari_plugin_hyw
|
|
3
|
+
Version: 3.5.0rc1
|
|
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-2.0-flash-exp
|
|
79
|
+
api_key: "your-or-api-key-here"
|
|
80
|
+
# Rendering Configuration
|
|
81
|
+
render_timeout_ms: 6000 # Browser wait timeout
|
|
82
|
+
render_image_timeout_ms: 3000 # Image load wait timeout
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Commands
|
|
88
|
+
|
|
89
|
+
- **Text Query**
|
|
90
|
+
```text
|
|
91
|
+
/q What's the latest news on Rust 1.83?
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **Image Analysis**
|
|
95
|
+
*(Send an image with command, or reply to an image)*
|
|
96
|
+
```text
|
|
97
|
+
/q [Image] Explain this error.
|
|
98
|
+
```
|
|
99
|
+
- **Quote Query**
|
|
100
|
+
```text
|
|
101
|
+
[quote: User Message] /q
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- **Follow-up**
|
|
105
|
+
*Reply to the bot's message to continue the conversation.*
|
|
106
|
+
|
|
107
|
+
## Documentation for AI/LLMs
|
|
108
|
+
|
|
109
|
+
- [Instruction Guide (English)](docs/README_LLM_EN.md)
|
|
110
|
+
- [指导手册 (简体中文)](docs/README_LLM_CN.md)
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,87 @@
|
|
|
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-2.0-flash-exp
|
|
50
|
+
api_key: "your-or-api-key-here"
|
|
51
|
+
# Rendering Configuration
|
|
52
|
+
render_timeout_ms: 6000 # Browser wait timeout
|
|
53
|
+
render_image_timeout_ms: 3000 # Image load wait timeout
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
### Commands
|
|
59
|
+
|
|
60
|
+
- **Text Query**
|
|
61
|
+
```text
|
|
62
|
+
/q What's the latest news on Rust 1.83?
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **Image Analysis**
|
|
66
|
+
*(Send an image with command, or reply to an image)*
|
|
67
|
+
```text
|
|
68
|
+
/q [Image] Explain this error.
|
|
69
|
+
```
|
|
70
|
+
- **Quote Query**
|
|
71
|
+
```text
|
|
72
|
+
[quote: User Message] /q
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **Follow-up**
|
|
76
|
+
*Reply to the bot's message to continue the conversation.*
|
|
77
|
+
|
|
78
|
+
## Documentation for AI/LLMs
|
|
79
|
+
|
|
80
|
+
- [Instruction Guide (English)](docs/README_LLM_EN.md)
|
|
81
|
+
- [指导手册 (简体中文)](docs/README_LLM_CN.md)
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
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.
|
|
7
|
+
version = "3.5.0-rc1"
|
|
8
8
|
description = "Use large language models to interpret chat messages"
|
|
9
9
|
authors = [{name = "kumoSleeping", email = "zjr2992@outlook.com"}]
|
|
10
10
|
dependencies = [
|
|
@@ -14,6 +14,7 @@ dependencies = [
|
|
|
14
14
|
"markdown>=3.10",
|
|
15
15
|
"crawl4ai>=0.7.8",
|
|
16
16
|
"jinja2>=3.0",
|
|
17
|
+
"ddgs>=9.10.0",
|
|
17
18
|
]
|
|
18
19
|
requires-python = ">=3.10"
|
|
19
20
|
readme = "README.md"
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Dict, Any, Optional, Union
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from arclet.alconna import Alconna, Args, AllParam, CommandMeta, Option, Arparma, MultiVar, store_true
|
|
6
|
+
from arclet.entari import metadata, listen, Session, plugin_config, BasicConfModel, plugin, command
|
|
7
|
+
from arclet.entari import MessageChain, Text, Image, MessageCreatedEvent, Quote, At
|
|
8
|
+
from satori.element import Custom
|
|
9
|
+
from loguru import logger
|
|
10
|
+
import arclet.letoderea as leto
|
|
11
|
+
from arclet.entari.event.command import CommandReceive
|
|
12
|
+
|
|
13
|
+
from .core.hyw import HYW
|
|
14
|
+
from .core.history import HistoryManager
|
|
15
|
+
from .core.render_vue import ContentRenderer
|
|
16
|
+
from .utils.misc import process_onebot_json, process_images, resolve_model_name
|
|
17
|
+
from arclet.entari.event.lifespan import Cleanup, Startup
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import secrets
|
|
21
|
+
import base64
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
|
|
25
|
+
class _RecentEventDeduper:
|
|
26
|
+
def __init__(self, ttl_seconds: float = 30.0, max_size: int = 2048):
|
|
27
|
+
self.ttl_seconds = ttl_seconds
|
|
28
|
+
self.max_size = max_size
|
|
29
|
+
self._seen: Dict[str, float] = {}
|
|
30
|
+
|
|
31
|
+
def seen_recently(self, key: str) -> bool:
|
|
32
|
+
now = time.time()
|
|
33
|
+
if len(self._seen) > self.max_size:
|
|
34
|
+
self._prune(now)
|
|
35
|
+
ts = self._seen.get(key)
|
|
36
|
+
if ts is None or now - ts > self.ttl_seconds:
|
|
37
|
+
self._seen[key] = now
|
|
38
|
+
return False
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
def _prune(self, now: float):
|
|
42
|
+
expired = [k for k, ts in self._seen.items() if now - ts > self.ttl_seconds]
|
|
43
|
+
for k in expired:
|
|
44
|
+
self._seen.pop(k, None)
|
|
45
|
+
if len(self._seen) > self.max_size:
|
|
46
|
+
for k, _ in sorted(self._seen.items(), key=lambda kv: kv[1])[: len(self._seen) - self.max_size]:
|
|
47
|
+
self._seen.pop(k, None)
|
|
48
|
+
|
|
49
|
+
_event_deduper = _RecentEventDeduper()
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class HywConfig(BasicConfModel):
|
|
53
|
+
admins: List[str] = field(default_factory=list)
|
|
54
|
+
models: List[Dict[str, Any]] = field(default_factory=list)
|
|
55
|
+
question_command: str = "/q"
|
|
56
|
+
model_name: Optional[str] = None
|
|
57
|
+
api_key: Optional[str] = None
|
|
58
|
+
base_url: str = "https://openrouter.ai/api/v1"
|
|
59
|
+
vision_model_name: Optional[str] = None
|
|
60
|
+
vision_api_key: Optional[str] = None
|
|
61
|
+
language: str = "Simplified Chinese"
|
|
62
|
+
vision_base_url: Optional[str] = None
|
|
63
|
+
instruct_model_name: Optional[str] = None
|
|
64
|
+
instruct_api_key: Optional[str] = None
|
|
65
|
+
instruct_base_url: Optional[str] = None
|
|
66
|
+
search_base_url: str = "https://lite.duckduckgo.com/lite/?q={query}"
|
|
67
|
+
image_search_base_url: str = "https://duckduckgo.com/?q={query}&iax=images&ia=images"
|
|
68
|
+
headless: bool = False
|
|
69
|
+
save_conversation: bool = False
|
|
70
|
+
icon: str = "openai"
|
|
71
|
+
render_timeout_ms: int = 6000
|
|
72
|
+
render_image_timeout_ms: int = 3000
|
|
73
|
+
extra_body: Optional[Dict[str, Any]] = None
|
|
74
|
+
vision_extra_body: Optional[Dict[str, Any]] = None
|
|
75
|
+
instruct_extra_body: Optional[Dict[str, Any]] = None
|
|
76
|
+
enable_browser_fallback: bool = False
|
|
77
|
+
reaction: bool = False
|
|
78
|
+
quote: bool = True
|
|
79
|
+
temperature: float = 0.4
|
|
80
|
+
# Billing configuration (price per million tokens)
|
|
81
|
+
input_price: Optional[float] = None # $ per 1M input tokens
|
|
82
|
+
output_price: Optional[float] = None # $ per 1M output tokens
|
|
83
|
+
# Vision model pricing overrides (defaults to main model pricing if not set)
|
|
84
|
+
vision_input_price: Optional[float] = None
|
|
85
|
+
vision_output_price: Optional[float] = None
|
|
86
|
+
# Instruct model pricing overrides (defaults to main model pricing if not set)
|
|
87
|
+
instruct_input_price: Optional[float] = None
|
|
88
|
+
instruct_output_price: Optional[float] = None
|
|
89
|
+
# Provider Names
|
|
90
|
+
search_name: str = "DuckDuckGo"
|
|
91
|
+
search_provider: str = "crawl4ai" # crawl4ai | httpx | ddgs
|
|
92
|
+
model_provider: Optional[str] = None
|
|
93
|
+
vision_model_provider: Optional[str] = None
|
|
94
|
+
instruct_model_provider: Optional[str] = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
conf = plugin_config(HywConfig)
|
|
99
|
+
history_manager = HistoryManager()
|
|
100
|
+
renderer = ContentRenderer()
|
|
101
|
+
hyw = HYW(config=conf)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@listen(Startup)
|
|
105
|
+
async def _hyw_startup():
|
|
106
|
+
try:
|
|
107
|
+
# Pre-launch browser
|
|
108
|
+
logger.info("HYW: Pre-launching renderer browser...")
|
|
109
|
+
await renderer.start()
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.warning(f"HYW: Renderer warm-up failed: {e}")
|
|
112
|
+
|
|
113
|
+
@listen(Cleanup, once=True)
|
|
114
|
+
async def _hyw_cleanup():
|
|
115
|
+
try:
|
|
116
|
+
await hyw.close()
|
|
117
|
+
await renderer.close()
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.warning(f"HYW cleanup error: {e}")
|
|
120
|
+
|
|
121
|
+
class GlobalCache:
|
|
122
|
+
models_image_path: Optional[str] = None
|
|
123
|
+
|
|
124
|
+
global_cache = GlobalCache()
|
|
125
|
+
|
|
126
|
+
from satori.exception import ActionFailed
|
|
127
|
+
from satori.adapters.onebot11.reverse import _Connection
|
|
128
|
+
|
|
129
|
+
# Monkeypatch to suppress ActionFailed for get_msg
|
|
130
|
+
original_call_api = _Connection.call_api
|
|
131
|
+
|
|
132
|
+
async def patched_call_api(self, action: str, params: dict = None):
|
|
133
|
+
try:
|
|
134
|
+
return await original_call_api(self, action, params)
|
|
135
|
+
except ActionFailed as e:
|
|
136
|
+
if action == "get_msg":
|
|
137
|
+
logger.warning(f"Suppressed ActionFailed for get_msg: {e}")
|
|
138
|
+
return None
|
|
139
|
+
raise e
|
|
140
|
+
|
|
141
|
+
_Connection.call_api = patched_call_api
|
|
142
|
+
|
|
143
|
+
EMOJI_TO_CODE = {
|
|
144
|
+
"✨": "10024",
|
|
145
|
+
"✅": "10004",
|
|
146
|
+
"❌": "10060"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async def react(session: Session, emoji: str):
|
|
150
|
+
if not conf.reaction: return
|
|
151
|
+
try:
|
|
152
|
+
if session.event.login.platform == "onebot":
|
|
153
|
+
code = EMOJI_TO_CODE.get(emoji, "10024")
|
|
154
|
+
# OneBot specific reaction
|
|
155
|
+
await session.account.protocol.call_api(
|
|
156
|
+
"internal/set_group_reaction",
|
|
157
|
+
{
|
|
158
|
+
"group_id": str(session.guild.id),
|
|
159
|
+
"message_id": str(session.event.message.id),
|
|
160
|
+
"code": code,
|
|
161
|
+
"is_add": True
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
# Standard Satori reaction
|
|
166
|
+
await session.reaction_create(emoji=emoji)
|
|
167
|
+
except ActionFailed:
|
|
168
|
+
pass
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.warning(f"Reaction failed: {e}")
|
|
171
|
+
|
|
172
|
+
async def process_request(session: Session[MessageCreatedEvent], all_param: Optional[MessageChain] = None,
|
|
173
|
+
selected_model: Optional[str] = None, selected_vision_model: Optional[str] = None,
|
|
174
|
+
conversation_key_override: Optional[str] = None, local_mode: bool = False):
|
|
175
|
+
logger.info(f"Processing request: {all_param}")
|
|
176
|
+
mc = MessageChain(all_param)
|
|
177
|
+
logger.info(f"reply: {session.reply}")
|
|
178
|
+
if session.reply:
|
|
179
|
+
try:
|
|
180
|
+
# Check if reply is from self (the bot)
|
|
181
|
+
# 1. Check by Message ID (reliable for bot's own messages if recorded)
|
|
182
|
+
reply_msg_id = str(session.reply.origin.id) if hasattr(session.reply.origin, 'id') else None
|
|
183
|
+
is_bot = False
|
|
184
|
+
|
|
185
|
+
if reply_msg_id and history_manager.is_bot_message(reply_msg_id):
|
|
186
|
+
is_bot = True
|
|
187
|
+
logger.info(f"Reply target {reply_msg_id} identified as bot message via history")
|
|
188
|
+
|
|
189
|
+
if is_bot:
|
|
190
|
+
logger.info("Reply is from me - ignoring content")
|
|
191
|
+
else:
|
|
192
|
+
logger.info(f"Reply is from user (or unknown) - including content")
|
|
193
|
+
mc.extend(MessageChain(" ") + session.reply.origin.message)
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.warning(f"Failed to process reply origin: {e}")
|
|
196
|
+
mc.extend(MessageChain(" ") + session.reply.origin.message)
|
|
197
|
+
|
|
198
|
+
# Filter and reconstruct MessageChain
|
|
199
|
+
filtered_elements = mc.get(Text) + mc.get(Image) + mc.get(Custom)
|
|
200
|
+
mc = MessageChain(filtered_elements)
|
|
201
|
+
logger.info(f"mc: {mc}")
|
|
202
|
+
|
|
203
|
+
text_content = str(mc.get(Text)).strip()
|
|
204
|
+
# Remove HTML image tags from text content to prevent "unreasonable code behavior"
|
|
205
|
+
text_content = re.sub(r'<img[^>]+>', '', text_content, flags=re.IGNORECASE)
|
|
206
|
+
|
|
207
|
+
if not text_content and not mc.get(Image) and not mc.get(Custom):
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
# History & Context
|
|
211
|
+
hist_key = conversation_key_override
|
|
212
|
+
if not hist_key and session.reply and hasattr(session.reply.origin, 'id'):
|
|
213
|
+
hist_key = history_manager.get_conversation_id(str(session.reply.origin.id))
|
|
214
|
+
|
|
215
|
+
hist_payload = history_manager.get_history(hist_key) if hist_key else []
|
|
216
|
+
meta = history_manager.get_metadata(hist_key) if hist_key else {}
|
|
217
|
+
context_id = f"guild_{session.guild.id}" if session.guild else f"user_{session.user.id}"
|
|
218
|
+
|
|
219
|
+
if conf.reaction: await react(session, "✨")
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
msg_text = str(mc.get(Text)).strip() if mc.get(Text) else ""
|
|
223
|
+
msg_text = re.sub(r'<img[^>]+>', '', msg_text, flags=re.IGNORECASE)
|
|
224
|
+
|
|
225
|
+
# If message is empty but has images, use a placeholder
|
|
226
|
+
if not msg_text and (mc.get(Image) or mc.get(Custom)):
|
|
227
|
+
msg_text = "[图片]"
|
|
228
|
+
|
|
229
|
+
for custom in [e for e in mc if isinstance(e, Custom)]:
|
|
230
|
+
if custom.tag == 'onebot:json':
|
|
231
|
+
if decoded := process_onebot_json(custom.attributes()): msg_text += f"\n{decoded}"
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
# Model Selection (Step 1)
|
|
235
|
+
# Resolve model names from config if they are short names/keywords
|
|
236
|
+
model = selected_model or meta.get("model")
|
|
237
|
+
if model and model != "off":
|
|
238
|
+
resolved, err = resolve_model_name(model, conf.models)
|
|
239
|
+
if resolved:
|
|
240
|
+
model = resolved
|
|
241
|
+
elif err:
|
|
242
|
+
logger.warning(f"Model resolution warning for {model}: {err}")
|
|
243
|
+
|
|
244
|
+
vision_model = selected_vision_model or meta.get("vision_model")
|
|
245
|
+
if vision_model and vision_model != "off":
|
|
246
|
+
resolved_v, err_v = resolve_model_name(vision_model, conf.models)
|
|
247
|
+
if resolved_v:
|
|
248
|
+
vision_model = resolved_v
|
|
249
|
+
elif err_v:
|
|
250
|
+
logger.warning(f"Vision model resolution warning for {vision_model}: {err_v}")
|
|
251
|
+
|
|
252
|
+
images, err = await process_images(mc, vision_model)
|
|
253
|
+
|
|
254
|
+
# Call Agent (Step 1)
|
|
255
|
+
# Sanitize user_input: use extracted text only
|
|
256
|
+
safe_input = msg_text
|
|
257
|
+
|
|
258
|
+
resp = await hyw.agent(safe_input, conversation_history=hist_payload, images=images,
|
|
259
|
+
selected_model=model, selected_vision_model=vision_model, local_mode=local_mode)
|
|
260
|
+
|
|
261
|
+
# Step 1 Results
|
|
262
|
+
step1_vision_model = resp.get("vision_model_used")
|
|
263
|
+
step1_model = resp.get("model_used")
|
|
264
|
+
step1_history = resp.get("conversation_history", [])
|
|
265
|
+
step1_stats = resp.get("stats", {})
|
|
266
|
+
|
|
267
|
+
final_resp = resp
|
|
268
|
+
|
|
269
|
+
# Step 2 (Optional)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Extract Response Data
|
|
274
|
+
content = final_resp.get("llm_response", "")
|
|
275
|
+
structured = final_resp.get("structured_response", {})
|
|
276
|
+
|
|
277
|
+
# Render
|
|
278
|
+
import tempfile
|
|
279
|
+
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tf:
|
|
280
|
+
output_path = tf.name
|
|
281
|
+
model_used = final_resp.get("model_used")
|
|
282
|
+
|
|
283
|
+
# Determine session short code
|
|
284
|
+
if hist_key:
|
|
285
|
+
display_session_id = history_manager.get_code_by_key(hist_key)
|
|
286
|
+
if not display_session_id:
|
|
287
|
+
display_session_id = history_manager.generate_short_code()
|
|
288
|
+
else:
|
|
289
|
+
display_session_id = history_manager.generate_short_code()
|
|
290
|
+
|
|
291
|
+
# Use stats_list if available, otherwise standard stats
|
|
292
|
+
stats_to_render = final_resp.get("stats_list", final_resp.get("stats", {}))
|
|
293
|
+
|
|
294
|
+
render_ok = await renderer.render(
|
|
295
|
+
markdown_content=content,
|
|
296
|
+
output_path=output_path,
|
|
297
|
+
stats=stats_to_render,
|
|
298
|
+
references=structured.get("references", []),
|
|
299
|
+
page_references=structured.get("page_references", []),
|
|
300
|
+
image_references=structured.get("image_references", []),
|
|
301
|
+
stages_used=final_resp.get("stages_used", []),
|
|
302
|
+
image_timeout=conf.render_image_timeout_ms,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Send & Save
|
|
306
|
+
if not render_ok:
|
|
307
|
+
logger.error("Render failed; skipping reply. Check Crawl4AI rendering status.")
|
|
308
|
+
if os.path.exists(output_path):
|
|
309
|
+
try:
|
|
310
|
+
os.remove(output_path)
|
|
311
|
+
except Exception as exc:
|
|
312
|
+
logger.warning(f"Failed to delete render output {output_path}: {exc}")
|
|
313
|
+
sent = None
|
|
314
|
+
else:
|
|
315
|
+
# Convert to base64
|
|
316
|
+
with open(output_path, "rb") as f:
|
|
317
|
+
img_data = base64.b64encode(f.read()).decode()
|
|
318
|
+
|
|
319
|
+
# Build single reply chain (image only now)
|
|
320
|
+
elements = []
|
|
321
|
+
elements.append(Image(src=f'data:image/png;base64,{img_data}'))
|
|
322
|
+
|
|
323
|
+
msg_chain = MessageChain(*elements)
|
|
324
|
+
|
|
325
|
+
if conf.quote:
|
|
326
|
+
msg_chain = MessageChain(Quote(session.event.message.id)) + msg_chain
|
|
327
|
+
|
|
328
|
+
# Use reply_to instead of manual Quote insertion to avoid ActionFailed errors
|
|
329
|
+
sent = await session.send(msg_chain)
|
|
330
|
+
|
|
331
|
+
sent_id = next((str(e.id) for e in sent if hasattr(e, 'id')), None) if sent else None
|
|
332
|
+
msg_id = str(session.event.message.id) if hasattr(session.event, 'message') else str(session.event.id)
|
|
333
|
+
related = [msg_id] + ([str(session.reply.origin.id)] if session.reply and hasattr(session.reply.origin, 'id') else [])
|
|
334
|
+
|
|
335
|
+
history_manager.remember(
|
|
336
|
+
sent_id,
|
|
337
|
+
final_resp.get("conversation_history", []),
|
|
338
|
+
related,
|
|
339
|
+
{
|
|
340
|
+
"model": model_used,
|
|
341
|
+
"trace_markdown": final_resp.get("trace_markdown"),
|
|
342
|
+
},
|
|
343
|
+
context_id,
|
|
344
|
+
code=display_session_id,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if conf.save_conversation and sent_id:
|
|
348
|
+
history_manager.save_to_disk(sent_id)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.exception(f"Error: {e}")
|
|
353
|
+
err_msg = f"Error: {e}"
|
|
354
|
+
if conf.quote:
|
|
355
|
+
await session.send([Quote(session.event.message.id), err_msg])
|
|
356
|
+
else:
|
|
357
|
+
await session.send(err_msg)
|
|
358
|
+
|
|
359
|
+
# Save conversation on error if response was generated
|
|
360
|
+
if 'resp' in locals() and resp and conf.save_conversation:
|
|
361
|
+
try:
|
|
362
|
+
# Use a temporary ID for error cases
|
|
363
|
+
error_id = f"error_{int(time.time())}_{secrets.token_hex(4)}"
|
|
364
|
+
history_manager.remember(error_id, resp.get("conversation_history", []), [], {"model": model_used if 'model_used' in locals() else "unknown", "error": str(e)}, context_id, code=display_session_id if 'display_session_id' in locals() else None)
|
|
365
|
+
history_manager.save_to_disk(error_id)
|
|
366
|
+
logger.info(f"Saved error conversation to {error_id}")
|
|
367
|
+
except Exception as save_err:
|
|
368
|
+
logger.error(f"Failed to save error conversation: {save_err}")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# Main Command (Question)
|
|
373
|
+
alc = Alconna(
|
|
374
|
+
conf.question_command,
|
|
375
|
+
Args["all_param;?", AllParam],
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
@command.on(alc)
|
|
379
|
+
async def handle_question_command(session: Session[MessageCreatedEvent], result: Arparma):
|
|
380
|
+
"""Handle main Question command"""
|
|
381
|
+
try:
|
|
382
|
+
# logger.info(f"Question Command Triggered. Message: {result}")
|
|
383
|
+
mid = str(session.event.message.id) if getattr(session.event, "message", None) else str(session.event.id)
|
|
384
|
+
dedupe_key = f"{getattr(session.account, 'id', 'account')}:{mid}"
|
|
385
|
+
if _event_deduper.seen_recently(dedupe_key):
|
|
386
|
+
logger.warning(f"Duplicate command event ignored: {dedupe_key}")
|
|
387
|
+
return
|
|
388
|
+
except Exception:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
logger.info(f"Question Command Triggered. Message: {session.event.message}")
|
|
392
|
+
|
|
393
|
+
args = result.all_matched_args
|
|
394
|
+
logger.info(f"Matched Args: {args}")
|
|
395
|
+
|
|
396
|
+
# Only all_param is supported now
|
|
397
|
+
# Context ID for history lookup is automatically handled in process_request
|
|
398
|
+
|
|
399
|
+
await process_request(session, args.get("all_param"), selected_model=None, selected_vision_model=None, conversation_key_override=None, local_mode=False)
|
|
400
|
+
|
|
401
|
+
metadata("hyw", author=[{"name": "kumoSleeping", "email": "zjr2992@outlook.com"}], version="3.5.0-rc1", config=HywConfig)
|
|
402
|
+
|
|
403
|
+
@leto.on(CommandReceive)
|
|
404
|
+
async def remove_at(content: MessageChain):
|
|
405
|
+
content = content.lstrip(At)
|
|
406
|
+
return content
|