consultant-agent 0.1.0__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.
- consultant_agent-0.1.0/CHANGELOG.md +29 -0
- consultant_agent-0.1.0/LICENSE +21 -0
- consultant_agent-0.1.0/PKG-INFO +195 -0
- consultant_agent-0.1.0/README.md +152 -0
- consultant_agent-0.1.0/pyproject.toml +77 -0
- consultant_agent-0.1.0/src/consultant_agent/__init__.py +15 -0
- consultant_agent-0.1.0/src/consultant_agent/agent.py +178 -0
- consultant_agent-0.1.0/src/consultant_agent/cli.py +128 -0
- consultant_agent-0.1.0/src/consultant_agent/tools/__init__.py +21 -0
- consultant_agent-0.1.0/src/consultant_agent/tools/analysis.py +111 -0
- consultant_agent-0.1.0/src/consultant_agent/tools/basic.py +45 -0
- consultant_agent-0.1.0/src/consultant_agent/tools/document.py +216 -0
- consultant_agent-0.1.0/src/consultant_agent/tools/search.py +78 -0
- consultant_agent-0.1.0/src/consultant_agent/version.py +1 -0
- consultant_agent-0.1.0/src/consultant_agent/web.py +86 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2024-07-08
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
- 🎉 首次发布
|
|
7
|
+
- ✨ 核心 Agent 功能
|
|
8
|
+
- 天气查询
|
|
9
|
+
- 数学计算
|
|
10
|
+
- 联网搜索
|
|
11
|
+
- 网页抓取
|
|
12
|
+
- 📄 文档处理
|
|
13
|
+
- PDF 阅读
|
|
14
|
+
- Word 阅读
|
|
15
|
+
- Excel 阅读
|
|
16
|
+
- PPT 阅读
|
|
17
|
+
- 📊 分析功能
|
|
18
|
+
- 内容总结
|
|
19
|
+
- 报告大纲生成
|
|
20
|
+
- 竞品对比
|
|
21
|
+
- 案例搜索
|
|
22
|
+
- 🖥️ 命令行工具
|
|
23
|
+
- `consultant` 交互式对话
|
|
24
|
+
- `consultant ask` 单次提问
|
|
25
|
+
- `consultant clear` 清空记忆
|
|
26
|
+
- 🔧 开发者 API
|
|
27
|
+
- `ConsultantAgent` 类
|
|
28
|
+
- `as_tool()` 方法,可嵌入其他 Agent
|
|
29
|
+
- 💾 对话记忆功能
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 xiaoboluo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: consultant-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 一个专业的咨询顾问 AI Agent,支持文档分析、联网搜索、报告生成
|
|
5
|
+
Project-URL: Homepage, https://github.com/xiaoboluo/consultant-agent
|
|
6
|
+
Project-URL: Repository, https://github.com/xiaoboluo/consultant-agent
|
|
7
|
+
Author-email: xiaoboluo <your@email.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent,ai,consultant,langchain,llm
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
20
|
+
Requires-Dist: ddgs>=6.0.0
|
|
21
|
+
Requires-Dist: langchain-openai>=0.1.0
|
|
22
|
+
Requires-Dist: langchain>=0.2.0
|
|
23
|
+
Requires-Dist: langgraph>=0.1.0
|
|
24
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
25
|
+
Requires-Dist: pandas>=2.0.0
|
|
26
|
+
Requires-Dist: pdfplumber>=0.10.0
|
|
27
|
+
Requires-Dist: python-docx>=1.0.0
|
|
28
|
+
Requires-Dist: python-pptx>=0.6.0
|
|
29
|
+
Requires-Dist: requests>=2.31.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: ocr
|
|
35
|
+
Requires-Dist: pillow>=10.0.0; extra == 'ocr'
|
|
36
|
+
Requires-Dist: pymupdf>=1.23.0; extra == 'ocr'
|
|
37
|
+
Requires-Dist: pytesseract>=0.3.10; extra == 'ocr'
|
|
38
|
+
Provides-Extra: web
|
|
39
|
+
Requires-Dist: fastapi>=0.110.0; extra == 'web'
|
|
40
|
+
Requires-Dist: gradio>=4.0.0; extra == 'web'
|
|
41
|
+
Requires-Dist: uvicorn>=0.27.0; extra == 'web'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# Consultant Agent 🤖
|
|
45
|
+
|
|
46
|
+
一个专业的咨询顾问 AI Agent,一行命令安装,开箱即用。
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/consultant-agent/)
|
|
49
|
+
[](https://www.python.org/downloads/)
|
|
50
|
+
[](https://opensource.org/licenses/MIT)
|
|
51
|
+
|
|
52
|
+
## ✨ 特性
|
|
53
|
+
|
|
54
|
+
- 🔍 **联网搜索** - 实时获取最新信息
|
|
55
|
+
- 📄 **文档阅读** - 支持 PDF、Word、Excel、PPT
|
|
56
|
+
- 📊 **数据分析** - Excel 数据处理与分析
|
|
57
|
+
- 📝 **内容生成** - 报告大纲、竞品对比、内容总结
|
|
58
|
+
- 🛠️ **易于集成** - 可作为工具嵌入其他 Agent
|
|
59
|
+
- 💾 **对话记忆** - 自动保存上下文
|
|
60
|
+
|
|
61
|
+
## 🚀 快速开始
|
|
62
|
+
|
|
63
|
+
### 安装
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install consultant-agent
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 设置 API Key
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Linux/Mac
|
|
73
|
+
export DEEPSEEK_API_KEY="sk-your-api-key"
|
|
74
|
+
|
|
75
|
+
# Windows
|
|
76
|
+
set DEEPSEEK_API_KEY=sk-your-api-key
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> 💡 去 [DeepSeek 开放平台](https://platform.deepseek.com/) 获取 API Key
|
|
80
|
+
|
|
81
|
+
### 使用
|
|
82
|
+
|
|
83
|
+
**命令行交互**
|
|
84
|
+
```bash
|
|
85
|
+
consultant
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**单次提问**
|
|
89
|
+
```bash
|
|
90
|
+
consultant ask "北京今天天气怎么样"
|
|
91
|
+
consultant ask "帮我生成一个 AI Agent 技术发展的报告大纲"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Python 代码**
|
|
95
|
+
```python
|
|
96
|
+
from consultant_agent import ConsultantAgent
|
|
97
|
+
|
|
98
|
+
agent = ConsultantAgent()
|
|
99
|
+
reply = agent.ask("分析一下新能源汽车行业趋势")
|
|
100
|
+
print(reply)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 📖 功能详解
|
|
104
|
+
|
|
105
|
+
### 命令行命令
|
|
106
|
+
|
|
107
|
+
| 命令 | 说明 | 示例 |
|
|
108
|
+
|------|------|------|
|
|
109
|
+
| `consultant` | 交互式对话 | - |
|
|
110
|
+
| `consultant ask "问题"` | 单次提问 | `consultant ask "上海天气"` |
|
|
111
|
+
| `consultant clear` | 清空记忆 | - |
|
|
112
|
+
|
|
113
|
+
### Python API
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from consultant_agent import ConsultantAgent
|
|
117
|
+
|
|
118
|
+
# 基础用法
|
|
119
|
+
agent = ConsultantAgent()
|
|
120
|
+
reply = agent.ask("你的问题")
|
|
121
|
+
|
|
122
|
+
# 自定义配置
|
|
123
|
+
agent = ConsultantAgent(
|
|
124
|
+
api_key="sk-xxx", # 可选,默认从环境变量读取
|
|
125
|
+
model="deepseek-chat", # 模型名称
|
|
126
|
+
temperature=0, # 温度参数
|
|
127
|
+
enable_memory=True, # 启用记忆
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# 清空记忆
|
|
131
|
+
agent.clear_memory()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 作为工具嵌入其他 Agent
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from consultant_agent import ConsultantAgent
|
|
138
|
+
from langgraph.prebuilt import create_react_agent
|
|
139
|
+
|
|
140
|
+
# 创建咨询顾问
|
|
141
|
+
consultant = ConsultantAgent()
|
|
142
|
+
|
|
143
|
+
# 转换为工具
|
|
144
|
+
consultant_tool = consultant.as_tool()
|
|
145
|
+
|
|
146
|
+
# 在你的 Agent 中使用
|
|
147
|
+
your_agent = create_react_agent(
|
|
148
|
+
your_llm,
|
|
149
|
+
tools=[consultant_tool, your_other_tools...]
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 🎯 支持的功能
|
|
154
|
+
|
|
155
|
+
| 功能 | 说明 | 示例 |
|
|
156
|
+
|------|------|------|
|
|
157
|
+
| 天气查询 | 全球城市天气 | "北京天气" |
|
|
158
|
+
| 数学计算 | 表达式计算 | "计算 123 * 456" |
|
|
159
|
+
| 联网搜索 | DuckDuckGo 搜索 | "搜索 AI Agent 最新进展" |
|
|
160
|
+
| 网页抓取 | 读取网页内容 | "读取 https://example.com" |
|
|
161
|
+
| PDF 阅读 | 提取 PDF 文字 | "读取 report.pdf" |
|
|
162
|
+
| Word 阅读 | 读取 docx 文档 | "读取 document.docx" |
|
|
163
|
+
| Excel 阅读 | 读取表格数据 | "读取 data.xlsx" |
|
|
164
|
+
| PPT 阅读 | 提取幻灯片文字 | "读取 slides.pptx" |
|
|
165
|
+
| 内容总结 | 多种总结风格 | "总结这篇文章" |
|
|
166
|
+
| 报告大纲 | 生成专业大纲 | "生成 XX 报告大纲" |
|
|
167
|
+
| 竞品对比 | 多维度对比表 | "对比 A、B、C 产品" |
|
|
168
|
+
| 案例搜索 | 查找行业案例 | "搜索数字化转型案例" |
|
|
169
|
+
|
|
170
|
+
## ⚙️ 环境变量
|
|
171
|
+
|
|
172
|
+
| 变量 | 必须 | 说明 |
|
|
173
|
+
|------|------|------|
|
|
174
|
+
| `DEEPSEEK_API_KEY` | ✅ | DeepSeek API Key |
|
|
175
|
+
|
|
176
|
+
## 📦 可选依赖
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# OCR 功能(识别图片/PDF 中的文字)
|
|
180
|
+
pip install consultant-agent[ocr]
|
|
181
|
+
|
|
182
|
+
# Web 界面
|
|
183
|
+
pip install consultant-agent[web]
|
|
184
|
+
|
|
185
|
+
# 完整安装
|
|
186
|
+
pip install consultant-agent[ocr,web]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 🤝 贡献
|
|
190
|
+
|
|
191
|
+
欢迎 Issue 和 PR!
|
|
192
|
+
|
|
193
|
+
## 📄 许可证
|
|
194
|
+
|
|
195
|
+
MIT License
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Consultant Agent 🤖
|
|
2
|
+
|
|
3
|
+
一个专业的咨询顾问 AI Agent,一行命令安装,开箱即用。
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/consultant-agent/)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
## ✨ 特性
|
|
10
|
+
|
|
11
|
+
- 🔍 **联网搜索** - 实时获取最新信息
|
|
12
|
+
- 📄 **文档阅读** - 支持 PDF、Word、Excel、PPT
|
|
13
|
+
- 📊 **数据分析** - Excel 数据处理与分析
|
|
14
|
+
- 📝 **内容生成** - 报告大纲、竞品对比、内容总结
|
|
15
|
+
- 🛠️ **易于集成** - 可作为工具嵌入其他 Agent
|
|
16
|
+
- 💾 **对话记忆** - 自动保存上下文
|
|
17
|
+
|
|
18
|
+
## 🚀 快速开始
|
|
19
|
+
|
|
20
|
+
### 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install consultant-agent
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 设置 API Key
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Linux/Mac
|
|
30
|
+
export DEEPSEEK_API_KEY="sk-your-api-key"
|
|
31
|
+
|
|
32
|
+
# Windows
|
|
33
|
+
set DEEPSEEK_API_KEY=sk-your-api-key
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> 💡 去 [DeepSeek 开放平台](https://platform.deepseek.com/) 获取 API Key
|
|
37
|
+
|
|
38
|
+
### 使用
|
|
39
|
+
|
|
40
|
+
**命令行交互**
|
|
41
|
+
```bash
|
|
42
|
+
consultant
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**单次提问**
|
|
46
|
+
```bash
|
|
47
|
+
consultant ask "北京今天天气怎么样"
|
|
48
|
+
consultant ask "帮我生成一个 AI Agent 技术发展的报告大纲"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Python 代码**
|
|
52
|
+
```python
|
|
53
|
+
from consultant_agent import ConsultantAgent
|
|
54
|
+
|
|
55
|
+
agent = ConsultantAgent()
|
|
56
|
+
reply = agent.ask("分析一下新能源汽车行业趋势")
|
|
57
|
+
print(reply)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 📖 功能详解
|
|
61
|
+
|
|
62
|
+
### 命令行命令
|
|
63
|
+
|
|
64
|
+
| 命令 | 说明 | 示例 |
|
|
65
|
+
|------|------|------|
|
|
66
|
+
| `consultant` | 交互式对话 | - |
|
|
67
|
+
| `consultant ask "问题"` | 单次提问 | `consultant ask "上海天气"` |
|
|
68
|
+
| `consultant clear` | 清空记忆 | - |
|
|
69
|
+
|
|
70
|
+
### Python API
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from consultant_agent import ConsultantAgent
|
|
74
|
+
|
|
75
|
+
# 基础用法
|
|
76
|
+
agent = ConsultantAgent()
|
|
77
|
+
reply = agent.ask("你的问题")
|
|
78
|
+
|
|
79
|
+
# 自定义配置
|
|
80
|
+
agent = ConsultantAgent(
|
|
81
|
+
api_key="sk-xxx", # 可选,默认从环境变量读取
|
|
82
|
+
model="deepseek-chat", # 模型名称
|
|
83
|
+
temperature=0, # 温度参数
|
|
84
|
+
enable_memory=True, # 启用记忆
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 清空记忆
|
|
88
|
+
agent.clear_memory()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 作为工具嵌入其他 Agent
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from consultant_agent import ConsultantAgent
|
|
95
|
+
from langgraph.prebuilt import create_react_agent
|
|
96
|
+
|
|
97
|
+
# 创建咨询顾问
|
|
98
|
+
consultant = ConsultantAgent()
|
|
99
|
+
|
|
100
|
+
# 转换为工具
|
|
101
|
+
consultant_tool = consultant.as_tool()
|
|
102
|
+
|
|
103
|
+
# 在你的 Agent 中使用
|
|
104
|
+
your_agent = create_react_agent(
|
|
105
|
+
your_llm,
|
|
106
|
+
tools=[consultant_tool, your_other_tools...]
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 🎯 支持的功能
|
|
111
|
+
|
|
112
|
+
| 功能 | 说明 | 示例 |
|
|
113
|
+
|------|------|------|
|
|
114
|
+
| 天气查询 | 全球城市天气 | "北京天气" |
|
|
115
|
+
| 数学计算 | 表达式计算 | "计算 123 * 456" |
|
|
116
|
+
| 联网搜索 | DuckDuckGo 搜索 | "搜索 AI Agent 最新进展" |
|
|
117
|
+
| 网页抓取 | 读取网页内容 | "读取 https://example.com" |
|
|
118
|
+
| PDF 阅读 | 提取 PDF 文字 | "读取 report.pdf" |
|
|
119
|
+
| Word 阅读 | 读取 docx 文档 | "读取 document.docx" |
|
|
120
|
+
| Excel 阅读 | 读取表格数据 | "读取 data.xlsx" |
|
|
121
|
+
| PPT 阅读 | 提取幻灯片文字 | "读取 slides.pptx" |
|
|
122
|
+
| 内容总结 | 多种总结风格 | "总结这篇文章" |
|
|
123
|
+
| 报告大纲 | 生成专业大纲 | "生成 XX 报告大纲" |
|
|
124
|
+
| 竞品对比 | 多维度对比表 | "对比 A、B、C 产品" |
|
|
125
|
+
| 案例搜索 | 查找行业案例 | "搜索数字化转型案例" |
|
|
126
|
+
|
|
127
|
+
## ⚙️ 环境变量
|
|
128
|
+
|
|
129
|
+
| 变量 | 必须 | 说明 |
|
|
130
|
+
|------|------|------|
|
|
131
|
+
| `DEEPSEEK_API_KEY` | ✅ | DeepSeek API Key |
|
|
132
|
+
|
|
133
|
+
## 📦 可选依赖
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# OCR 功能(识别图片/PDF 中的文字)
|
|
137
|
+
pip install consultant-agent[ocr]
|
|
138
|
+
|
|
139
|
+
# Web 界面
|
|
140
|
+
pip install consultant-agent[web]
|
|
141
|
+
|
|
142
|
+
# 完整安装
|
|
143
|
+
pip install consultant-agent[ocr,web]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🤝 贡献
|
|
147
|
+
|
|
148
|
+
欢迎 Issue 和 PR!
|
|
149
|
+
|
|
150
|
+
## 📄 许可证
|
|
151
|
+
|
|
152
|
+
MIT License
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "consultant-agent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "一个专业的咨询顾问 AI Agent,支持文档分析、联网搜索、报告生成"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "xiaoboluo", email = "your@email.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agent", "langchain", "consultant", "llm"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"langchain>=0.2.0",
|
|
28
|
+
"langchain-openai>=0.1.0",
|
|
29
|
+
"langgraph>=0.1.0",
|
|
30
|
+
"requests>=2.31.0",
|
|
31
|
+
"ddgs>=6.0.0",
|
|
32
|
+
"beautifulsoup4>=4.12.0",
|
|
33
|
+
"pdfplumber>=0.10.0",
|
|
34
|
+
"python-docx>=1.0.0",
|
|
35
|
+
"python-pptx>=0.6.0",
|
|
36
|
+
"pandas>=2.0.0",
|
|
37
|
+
"openpyxl>=3.1.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
ocr = [
|
|
42
|
+
"pytesseract>=0.3.10",
|
|
43
|
+
"Pillow>=10.0.0",
|
|
44
|
+
"PyMuPDF>=1.23.0",
|
|
45
|
+
]
|
|
46
|
+
web = [
|
|
47
|
+
"gradio>=4.0.0",
|
|
48
|
+
"fastapi>=0.110.0",
|
|
49
|
+
"uvicorn>=0.27.0",
|
|
50
|
+
]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8.0.0",
|
|
53
|
+
"black>=24.0.0",
|
|
54
|
+
"ruff>=0.3.0",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
# 命令行入口
|
|
58
|
+
[project.scripts]
|
|
59
|
+
consultant = "consultant_agent.cli:main"
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
Homepage = "https://github.com/xiaoboluo/consultant-agent"
|
|
63
|
+
Repository = "https://github.com/xiaoboluo/consultant-agent"
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.wheel]
|
|
66
|
+
packages = ["src/consultant_agent"]
|
|
67
|
+
|
|
68
|
+
[tool.hatch.build.targets.sdist]
|
|
69
|
+
exclude = [
|
|
70
|
+
"venv",
|
|
71
|
+
".venv",
|
|
72
|
+
"__pycache__",
|
|
73
|
+
"*.pyc",
|
|
74
|
+
".git",
|
|
75
|
+
"dist",
|
|
76
|
+
"build",
|
|
77
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Consultant Agent - 专业的咨询顾问 AI Agent
|
|
3
|
+
|
|
4
|
+
快速开始:
|
|
5
|
+
from consultant_agent import ConsultantAgent
|
|
6
|
+
|
|
7
|
+
agent = ConsultantAgent(api_key="sk-xxx")
|
|
8
|
+
reply = agent.ask("帮我分析新能源汽车行业")
|
|
9
|
+
print(reply)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from consultant_agent.agent import ConsultantAgent
|
|
13
|
+
from consultant_agent.version import __version__
|
|
14
|
+
|
|
15
|
+
__all__ = ["ConsultantAgent", "__version__"]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ConsultantAgent - 核心 Agent 类
|
|
3
|
+
|
|
4
|
+
可以独立使用,也可以作为工具嵌入其他 Agent。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from langchain_openai import ChatOpenAI
|
|
12
|
+
from langchain_core.tools import tool
|
|
13
|
+
from langgraph.prebuilt import create_react_agent
|
|
14
|
+
|
|
15
|
+
from consultant_agent.tools import get_all_tools
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConsultantAgent:
|
|
19
|
+
"""
|
|
20
|
+
咨询顾问 AI Agent
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
>>> agent = ConsultantAgent(api_key="sk-xxx")
|
|
24
|
+
>>> reply = agent.ask("北京今天天气怎么样")
|
|
25
|
+
>>> print(reply)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
api_key: Optional[str] = None,
|
|
31
|
+
model: str = "deepseek-chat",
|
|
32
|
+
base_url: str = "https://api.deepseek.com",
|
|
33
|
+
temperature: float = 0,
|
|
34
|
+
memory_file: Optional[str] = None,
|
|
35
|
+
enable_memory: bool = True,
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
初始化 Agent
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
api_key: DeepSeek API Key,不传则从环境变量 DEEPSEEK_API_KEY 读取
|
|
42
|
+
model: 模型名称
|
|
43
|
+
base_url: API 地址
|
|
44
|
+
temperature: 温度参数
|
|
45
|
+
memory_file: 记忆文件路径,None 则使用默认路径
|
|
46
|
+
enable_memory: 是否启用记忆功能
|
|
47
|
+
"""
|
|
48
|
+
# API Key
|
|
49
|
+
self.api_key = api_key or os.environ.get("DEEPSEEK_API_KEY")
|
|
50
|
+
if not self.api_key:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"需要提供 api_key 或设置环境变量 DEEPSEEK_API_KEY\n"
|
|
53
|
+
"export DEEPSEEK_API_KEY='sk-xxx'"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# LLM
|
|
57
|
+
self.llm = ChatOpenAI(
|
|
58
|
+
model=model,
|
|
59
|
+
base_url=base_url,
|
|
60
|
+
api_key=self.api_key,
|
|
61
|
+
temperature=temperature,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# 记忆
|
|
65
|
+
self.enable_memory = enable_memory
|
|
66
|
+
if memory_file:
|
|
67
|
+
self.memory_file = memory_file
|
|
68
|
+
else:
|
|
69
|
+
home = os.path.expanduser("~")
|
|
70
|
+
self.memory_file = os.path.join(home, ".consultant_agent", "memory.json")
|
|
71
|
+
|
|
72
|
+
# 确保目录存在
|
|
73
|
+
os.makedirs(os.path.dirname(self.memory_file), exist_ok=True)
|
|
74
|
+
|
|
75
|
+
# 工具
|
|
76
|
+
self.tools = get_all_tools(self.llm)
|
|
77
|
+
|
|
78
|
+
# System Prompt
|
|
79
|
+
self.system_prompt = """你是一个专业的咨询顾问 AI 助手。
|
|
80
|
+
|
|
81
|
+
你的能力:
|
|
82
|
+
- 查询天气、计算数学
|
|
83
|
+
- 联网搜索最新信息
|
|
84
|
+
- 读取文档(PDF、Word、Excel、PPT)
|
|
85
|
+
- 总结分析内容、生成报告大纲
|
|
86
|
+
- 竞品对比分析
|
|
87
|
+
|
|
88
|
+
请用中文回答,保持专业、简洁。
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# 创建 Agent
|
|
92
|
+
self._agent = create_react_agent(
|
|
93
|
+
self.llm,
|
|
94
|
+
self.tools,
|
|
95
|
+
prompt=self.system_prompt,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def ask(self, question: str) -> str:
|
|
99
|
+
"""
|
|
100
|
+
向 Agent 提问
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
question: 问题或任务
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Agent 的回复
|
|
107
|
+
"""
|
|
108
|
+
# 加载记忆
|
|
109
|
+
memory = self._load_memory() if self.enable_memory else []
|
|
110
|
+
|
|
111
|
+
# 构建消息
|
|
112
|
+
messages = []
|
|
113
|
+
for msg in memory:
|
|
114
|
+
messages.append((msg["role"], msg["content"]))
|
|
115
|
+
messages.append(("user", question))
|
|
116
|
+
|
|
117
|
+
# 调用 Agent
|
|
118
|
+
result = self._agent.invoke({"messages": messages})
|
|
119
|
+
reply = result["messages"][-1].content
|
|
120
|
+
|
|
121
|
+
# 保存记忆
|
|
122
|
+
if self.enable_memory:
|
|
123
|
+
memory.append({"role": "user", "content": question})
|
|
124
|
+
memory.append({"role": "assistant", "content": reply})
|
|
125
|
+
self._save_memory(memory)
|
|
126
|
+
|
|
127
|
+
return reply
|
|
128
|
+
|
|
129
|
+
def clear_memory(self):
|
|
130
|
+
"""清空记忆"""
|
|
131
|
+
self._save_memory([])
|
|
132
|
+
|
|
133
|
+
def as_tool(self):
|
|
134
|
+
"""
|
|
135
|
+
将 Agent 转换为 LangChain Tool,可嵌入其他 Agent
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
>>> consultant = ConsultantAgent(api_key="sk-xxx")
|
|
139
|
+
>>> tool = consultant.as_tool()
|
|
140
|
+
>>> # 在其他 Agent 中使用
|
|
141
|
+
>>> other_agent = create_react_agent(llm, [tool, ...])
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
@tool
|
|
145
|
+
def consultant_agent(question: str) -> str:
|
|
146
|
+
"""
|
|
147
|
+
向咨询顾问 Agent 提问。擅长:
|
|
148
|
+
- 读取文档(PDF/Word/Excel/PPT)
|
|
149
|
+
- 联网搜索最新信息
|
|
150
|
+
- 生成报告大纲、竞品分析
|
|
151
|
+
- 内容总结分析
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
question: 问题或任务描述
|
|
155
|
+
"""
|
|
156
|
+
return self.ask(question)
|
|
157
|
+
|
|
158
|
+
return consultant_agent
|
|
159
|
+
|
|
160
|
+
def _load_memory(self) -> list:
|
|
161
|
+
"""加载记忆"""
|
|
162
|
+
if os.path.exists(self.memory_file):
|
|
163
|
+
try:
|
|
164
|
+
with open(self.memory_file, "r", encoding="utf-8") as f:
|
|
165
|
+
data = json.load(f)
|
|
166
|
+
return data.get("messages", [])[-100:] # 最多保留 100 条
|
|
167
|
+
except:
|
|
168
|
+
pass
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
def _save_memory(self, messages: list):
|
|
172
|
+
"""保存记忆"""
|
|
173
|
+
data = {
|
|
174
|
+
"messages": messages[-100:], # 最多保留 100 条
|
|
175
|
+
"updated_at": datetime.now().isoformat(),
|
|
176
|
+
}
|
|
177
|
+
with open(self.memory_file, "w", encoding="utf-8") as f:
|
|
178
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
命令行入口
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
consultant # 交互式对话
|
|
6
|
+
consultant ask "问题" # 单次提问
|
|
7
|
+
consultant web # 启动 Web 界面
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
prog="consultant",
|
|
18
|
+
description="咨询顾问 AI Agent - 专业的文档分析和报告生成助手",
|
|
19
|
+
)
|
|
20
|
+
subparsers = parser.add_subparsers(dest="command", help="可用命令")
|
|
21
|
+
|
|
22
|
+
# ask 命令
|
|
23
|
+
ask_parser = subparsers.add_parser("ask", help="单次提问")
|
|
24
|
+
ask_parser.add_argument("question", help="问题内容")
|
|
25
|
+
|
|
26
|
+
# web 命令
|
|
27
|
+
web_parser = subparsers.add_parser("web", help="启动 Web 界面")
|
|
28
|
+
web_parser.add_argument("--port", type=int, default=7860, help="端口号")
|
|
29
|
+
web_parser.add_argument("--share", action="store_true", help="生成公网链接")
|
|
30
|
+
|
|
31
|
+
# clear 命令
|
|
32
|
+
clear_parser = subparsers.add_parser("clear", help="清空记忆")
|
|
33
|
+
|
|
34
|
+
args = parser.parse_args()
|
|
35
|
+
|
|
36
|
+
# 检查 API Key
|
|
37
|
+
api_key = os.environ.get("DEEPSEEK_API_KEY")
|
|
38
|
+
if not api_key:
|
|
39
|
+
print("❌ 错误:请设置环境变量 DEEPSEEK_API_KEY")
|
|
40
|
+
print("")
|
|
41
|
+
print(" export DEEPSEEK_API_KEY='sk-xxx'")
|
|
42
|
+
print("")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
if args.command == "ask":
|
|
46
|
+
# 单次提问
|
|
47
|
+
from consultant_agent import ConsultantAgent
|
|
48
|
+
|
|
49
|
+
agent = ConsultantAgent(api_key=api_key)
|
|
50
|
+
print(agent.ask(args.question))
|
|
51
|
+
|
|
52
|
+
elif args.command == "web":
|
|
53
|
+
# 启动 Web 界面
|
|
54
|
+
try:
|
|
55
|
+
from consultant_agent.web import launch_web
|
|
56
|
+
launch_web(port=args.port, share=args.share)
|
|
57
|
+
except ImportError:
|
|
58
|
+
print("❌ 需要安装 web 依赖:")
|
|
59
|
+
print(" pip install consultant-agent[web]")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
elif args.command == "clear":
|
|
63
|
+
# 清空记忆
|
|
64
|
+
from consultant_agent import ConsultantAgent
|
|
65
|
+
|
|
66
|
+
agent = ConsultantAgent(api_key=api_key)
|
|
67
|
+
agent.clear_memory()
|
|
68
|
+
print("✅ 记忆已清空")
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
# 交互式对话
|
|
72
|
+
interactive_mode(api_key)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def interactive_mode(api_key: str):
|
|
76
|
+
"""交互式对话模式"""
|
|
77
|
+
from consultant_agent import ConsultantAgent
|
|
78
|
+
|
|
79
|
+
print("=" * 50)
|
|
80
|
+
print("🤖 咨询顾问 Agent")
|
|
81
|
+
print("=" * 50)
|
|
82
|
+
print("输入问题开始对话,输入 'quit' 退出")
|
|
83
|
+
print("=" * 50)
|
|
84
|
+
print("")
|
|
85
|
+
|
|
86
|
+
agent = ConsultantAgent(api_key=api_key)
|
|
87
|
+
|
|
88
|
+
while True:
|
|
89
|
+
try:
|
|
90
|
+
user_input = input("你: ").strip()
|
|
91
|
+
|
|
92
|
+
if not user_input:
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if user_input.lower() in ["quit", "exit", "退出", "bye"]:
|
|
96
|
+
print("👋 再见!")
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
if user_input == "/clear":
|
|
100
|
+
agent.clear_memory()
|
|
101
|
+
print("✅ 记忆已清空\n")
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if user_input == "/help":
|
|
105
|
+
print("""
|
|
106
|
+
可用命令:
|
|
107
|
+
/clear - 清空对话记忆
|
|
108
|
+
/help - 显示帮助
|
|
109
|
+
quit - 退出程序
|
|
110
|
+
|
|
111
|
+
或者直接输入问题开始对话!
|
|
112
|
+
""")
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
# 调用 Agent
|
|
116
|
+
print("思考中...")
|
|
117
|
+
reply = agent.ask(user_input)
|
|
118
|
+
print(f"\n助手: {reply}\n")
|
|
119
|
+
|
|
120
|
+
except KeyboardInterrupt:
|
|
121
|
+
print("\n👋 再见!")
|
|
122
|
+
break
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"❌ 出错了:{e}\n")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工具集合
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from consultant_agent.tools.basic import get_basic_tools
|
|
6
|
+
from consultant_agent.tools.search import get_search_tools
|
|
7
|
+
from consultant_agent.tools.document import get_document_tools
|
|
8
|
+
from consultant_agent.tools.analysis import get_analysis_tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_all_tools(llm=None):
|
|
12
|
+
"""获取所有工具"""
|
|
13
|
+
tools = []
|
|
14
|
+
tools.extend(get_basic_tools())
|
|
15
|
+
tools.extend(get_search_tools())
|
|
16
|
+
tools.extend(get_document_tools())
|
|
17
|
+
tools.extend(get_analysis_tools(llm))
|
|
18
|
+
return tools
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["get_all_tools"]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
分析工具:总结、报告大纲、竞品对比
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from langchain_core.tools import tool
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_analysis_tools(llm=None):
|
|
9
|
+
"""获取分析工具列表,需要传入 LLM 实例"""
|
|
10
|
+
|
|
11
|
+
if llm is None:
|
|
12
|
+
return []
|
|
13
|
+
|
|
14
|
+
@tool
|
|
15
|
+
def summarize(content: str, style: str = "要点") -> str:
|
|
16
|
+
"""
|
|
17
|
+
总结归纳内容。
|
|
18
|
+
content: 需要总结的文本
|
|
19
|
+
style: 总结风格 - "要点"(3-5个要点)、"简述"(一段话)、"详细"(分层次)
|
|
20
|
+
"""
|
|
21
|
+
style_prompts = {
|
|
22
|
+
"要点": "请将以下内容总结为3-5个核心要点:",
|
|
23
|
+
"简述": "请用一段话(100字以内)概括以下内容:",
|
|
24
|
+
"详细": "请对以下内容进行分层次的详细总结:",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
prompt = style_prompts.get(style, style_prompts["要点"])
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
if len(content) > 6000:
|
|
31
|
+
content = content[:6000] + "..."
|
|
32
|
+
|
|
33
|
+
response = llm.invoke([("user", f"{prompt}\n\n{content}")])
|
|
34
|
+
return f"📋 总结({style}模式):\n\n{response.content}"
|
|
35
|
+
except Exception as e:
|
|
36
|
+
return f"总结失败:{e}"
|
|
37
|
+
|
|
38
|
+
@tool
|
|
39
|
+
def generate_outline(topic: str, report_type: str = "分析报告") -> str:
|
|
40
|
+
"""
|
|
41
|
+
根据主题生成报告大纲。
|
|
42
|
+
topic: 报告主题
|
|
43
|
+
report_type: 报告类型 - "分析报告"、"研究报告"、"咨询方案"
|
|
44
|
+
"""
|
|
45
|
+
prompt = f"""请为主题「{topic}」设计一个专业的{report_type}大纲框架。
|
|
46
|
+
|
|
47
|
+
要求:
|
|
48
|
+
1. 包含一级和二级标题
|
|
49
|
+
2. 每个部分简要说明应包含的内容
|
|
50
|
+
3. 逻辑严谨,结构完整"""
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
response = llm.invoke([("user", prompt)])
|
|
54
|
+
return f"📑 报告大纲 - {topic}\n\n{response.content}"
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return f"生成大纲失败:{e}"
|
|
57
|
+
|
|
58
|
+
@tool
|
|
59
|
+
def generate_comparison(topic: str, products: str, dimensions: str = "") -> str:
|
|
60
|
+
"""
|
|
61
|
+
生成竞品对比表。
|
|
62
|
+
topic: 对比主题
|
|
63
|
+
products: 要对比的产品,逗号分隔
|
|
64
|
+
dimensions: 对比维度,逗号分隔(可选)
|
|
65
|
+
"""
|
|
66
|
+
prompt = f"""请生成一份专业的竞品对比表。
|
|
67
|
+
|
|
68
|
+
对比主题:{topic}
|
|
69
|
+
对比产品:{products}
|
|
70
|
+
对比维度:{dimensions if dimensions else "请根据主题自动选择5-8个关键维度"}
|
|
71
|
+
|
|
72
|
+
要求:
|
|
73
|
+
1. 使用 Markdown 表格格式
|
|
74
|
+
2. 内容具体、有数据支撑
|
|
75
|
+
3. 在表格后添加对比总结(3-5个要点)"""
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
response = llm.invoke([("user", prompt)])
|
|
79
|
+
return f"📊 竞品对比 - {topic}\n\n{response.content}"
|
|
80
|
+
except Exception as e:
|
|
81
|
+
return f"生成对比表失败:{e}"
|
|
82
|
+
|
|
83
|
+
@tool
|
|
84
|
+
def search_cases(topic: str, industry: str = "") -> str:
|
|
85
|
+
"""
|
|
86
|
+
搜索相关案例。
|
|
87
|
+
topic: 案例主题
|
|
88
|
+
industry: 行业领域(可选)
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
from ddgs import DDGS
|
|
92
|
+
|
|
93
|
+
search_query = f"{topic} 案例 成功 企业 {industry}".strip()
|
|
94
|
+
|
|
95
|
+
with DDGS() as ddgs:
|
|
96
|
+
results = list(ddgs.text(search_query, max_results=5))
|
|
97
|
+
|
|
98
|
+
if not results:
|
|
99
|
+
return f"未找到关于 '{topic}' 的案例"
|
|
100
|
+
|
|
101
|
+
output = [f"🔍 案例搜索:{topic}"]
|
|
102
|
+
for i, r in enumerate(results, 1):
|
|
103
|
+
output.append(f"\n案例 {i}:{r['title']}")
|
|
104
|
+
output.append(f"来源:{r['href']}")
|
|
105
|
+
output.append(f"摘要:{r['body'][:200]}...")
|
|
106
|
+
|
|
107
|
+
return "\n".join(output)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return f"案例搜索失败:{e}"
|
|
110
|
+
|
|
111
|
+
return [summarize, generate_outline, generate_comparison, search_cases]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
基础工具:天气、计算
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from langchain_core.tools import tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@tool
|
|
10
|
+
def get_weather(city: str) -> str:
|
|
11
|
+
"""查询指定城市的天气。传入城市名称(中文或英文)。"""
|
|
12
|
+
try:
|
|
13
|
+
url = f"https://wttr.in/{city}?format=j1"
|
|
14
|
+
response = requests.get(url, timeout=10)
|
|
15
|
+
data = response.json()
|
|
16
|
+
|
|
17
|
+
current = data["current_condition"][0]
|
|
18
|
+
weather_desc = current.get("lang_zh", [{}])[0].get(
|
|
19
|
+
"value", current["weatherDesc"][0]["value"]
|
|
20
|
+
)
|
|
21
|
+
temp = current["temp_C"]
|
|
22
|
+
humidity = current["humidity"]
|
|
23
|
+
wind = current["windspeedKmph"]
|
|
24
|
+
|
|
25
|
+
return f"{city}天气:{weather_desc},温度 {temp}°C,湿度 {humidity}%,风速 {wind}km/h"
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return f"查询天气失败:{e}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@tool
|
|
31
|
+
def calculate(expression: str) -> str:
|
|
32
|
+
"""计算数学表达式。例如:'2+3*4' 或 '100/5' 或 '2**10'"""
|
|
33
|
+
try:
|
|
34
|
+
allowed = set("0123456789+-*/.() ")
|
|
35
|
+
if not all(c in allowed for c in expression):
|
|
36
|
+
return "只支持数学运算"
|
|
37
|
+
result = eval(expression)
|
|
38
|
+
return f"{expression} = {result}"
|
|
39
|
+
except Exception as e:
|
|
40
|
+
return f"计算出错:{e}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_basic_tools():
|
|
44
|
+
"""获取基础工具列表"""
|
|
45
|
+
return [get_weather, calculate]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""
|
|
2
|
+
文档工具:读取 PDF/Word/Excel/PPT
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from langchain_core.tools import tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@tool
|
|
10
|
+
def read_file(file_path: str) -> str:
|
|
11
|
+
"""读取本地文本文件内容。传入文件的完整路径。"""
|
|
12
|
+
try:
|
|
13
|
+
full_path = os.path.abspath(os.path.expanduser(file_path))
|
|
14
|
+
|
|
15
|
+
if not os.path.exists(full_path):
|
|
16
|
+
return f"文件不存在:{full_path}"
|
|
17
|
+
|
|
18
|
+
with open(full_path, "r", encoding="utf-8") as f:
|
|
19
|
+
content = f.read()
|
|
20
|
+
|
|
21
|
+
if len(content) > 5000:
|
|
22
|
+
return f"文件内容(前5000字):\n{content[:5000]}..."
|
|
23
|
+
return f"文件内容:\n{content}"
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return f"读取失败:{e}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@tool
|
|
29
|
+
def read_pdf(file_path: str, max_pages: int = 50) -> str:
|
|
30
|
+
"""
|
|
31
|
+
读取PDF文件内容。
|
|
32
|
+
file_path: PDF文件路径
|
|
33
|
+
max_pages: 最多读取的页数,默认50页
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
import pdfplumber
|
|
37
|
+
|
|
38
|
+
full_path = os.path.abspath(os.path.expanduser(file_path))
|
|
39
|
+
|
|
40
|
+
if not os.path.exists(full_path):
|
|
41
|
+
return f"文件不存在:{full_path}"
|
|
42
|
+
|
|
43
|
+
text_parts = []
|
|
44
|
+
|
|
45
|
+
with pdfplumber.open(full_path) as pdf:
|
|
46
|
+
total_pages = len(pdf.pages)
|
|
47
|
+
pages_to_read = min(max_pages, total_pages) if max_pages > 0 else total_pages
|
|
48
|
+
|
|
49
|
+
for i, page in enumerate(pdf.pages[:pages_to_read]):
|
|
50
|
+
page_text = page.extract_text()
|
|
51
|
+
if page_text:
|
|
52
|
+
text_parts.append(f"--- 第 {i+1} 页 ---\n{page_text}")
|
|
53
|
+
|
|
54
|
+
content = "\n\n".join(text_parts)
|
|
55
|
+
result = f"📑 PDF文件:{os.path.basename(full_path)}\n📄 共 {total_pages} 页(已读取 {pages_to_read} 页)\n\n{content}"
|
|
56
|
+
|
|
57
|
+
if len(result) > 10000:
|
|
58
|
+
result = result[:10000] + "\n\n...(内容过长,已截断)"
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return f"读取PDF失败:{e}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@tool
|
|
67
|
+
def read_docx(file_path: str) -> str:
|
|
68
|
+
"""读取Word文档(.docx)内容。"""
|
|
69
|
+
try:
|
|
70
|
+
from docx import Document
|
|
71
|
+
|
|
72
|
+
full_path = os.path.abspath(os.path.expanduser(file_path))
|
|
73
|
+
|
|
74
|
+
if not os.path.exists(full_path):
|
|
75
|
+
return f"文件不存在:{full_path}"
|
|
76
|
+
|
|
77
|
+
doc = Document(full_path)
|
|
78
|
+
text_parts = []
|
|
79
|
+
|
|
80
|
+
# 读取段落
|
|
81
|
+
for para in doc.paragraphs:
|
|
82
|
+
if para.text.strip():
|
|
83
|
+
text_parts.append(para.text)
|
|
84
|
+
|
|
85
|
+
# 读取表格
|
|
86
|
+
for table in doc.tables:
|
|
87
|
+
for row in table.rows:
|
|
88
|
+
row_text = " | ".join(cell.text.strip() for cell in row.cells)
|
|
89
|
+
if row_text.strip():
|
|
90
|
+
text_parts.append(row_text)
|
|
91
|
+
|
|
92
|
+
content = "\n\n".join(text_parts)
|
|
93
|
+
result = f"📄 Word文档:{os.path.basename(full_path)}\n\n{content}"
|
|
94
|
+
|
|
95
|
+
if len(result) > 8000:
|
|
96
|
+
result = result[:8000] + "\n\n...(内容过长,已截断)"
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return f"读取Word文档失败:{e}"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@tool
|
|
105
|
+
def read_excel(file_path: str, sheet_name: str = None, max_rows: int = 100) -> str:
|
|
106
|
+
"""
|
|
107
|
+
读取Excel文件内容。
|
|
108
|
+
file_path: Excel文件路径
|
|
109
|
+
sheet_name: 工作表名称,默认读取第一个
|
|
110
|
+
max_rows: 最多读取的行数
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
import pandas as pd
|
|
114
|
+
|
|
115
|
+
full_path = os.path.abspath(os.path.expanduser(file_path))
|
|
116
|
+
|
|
117
|
+
if not os.path.exists(full_path):
|
|
118
|
+
return f"文件不存在:{full_path}"
|
|
119
|
+
|
|
120
|
+
xlsx = pd.ExcelFile(full_path)
|
|
121
|
+
sheet_names = xlsx.sheet_names
|
|
122
|
+
|
|
123
|
+
result = [f"📊 Excel文件:{os.path.basename(full_path)}"]
|
|
124
|
+
result.append(f"📑 工作表:{', '.join(sheet_names)}\n")
|
|
125
|
+
|
|
126
|
+
target_sheet = sheet_name if sheet_name else sheet_names[0]
|
|
127
|
+
|
|
128
|
+
if target_sheet not in sheet_names:
|
|
129
|
+
return f"工作表 '{target_sheet}' 不存在,可用:{', '.join(sheet_names)}"
|
|
130
|
+
|
|
131
|
+
df = pd.read_excel(full_path, sheet_name=target_sheet)
|
|
132
|
+
|
|
133
|
+
total_rows = len(df)
|
|
134
|
+
result.append(f"【{target_sheet}】共 {total_rows} 行 × {len(df.columns)} 列")
|
|
135
|
+
result.append(f"列名:{', '.join(str(col) for col in df.columns)}\n")
|
|
136
|
+
|
|
137
|
+
if total_rows > max_rows:
|
|
138
|
+
df = df.head(max_rows)
|
|
139
|
+
result.append(f"(仅显示前 {max_rows} 行)\n")
|
|
140
|
+
|
|
141
|
+
result.append(df.to_string(index=False))
|
|
142
|
+
|
|
143
|
+
content = "\n".join(result)
|
|
144
|
+
if len(content) > 10000:
|
|
145
|
+
content = content[:10000] + "\n\n...(内容过长,已截断)"
|
|
146
|
+
|
|
147
|
+
return content
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
return f"读取Excel失败:{e}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@tool
|
|
154
|
+
def read_pptx(file_path: str) -> str:
|
|
155
|
+
"""读取PPT文件内容。"""
|
|
156
|
+
try:
|
|
157
|
+
from pptx import Presentation
|
|
158
|
+
|
|
159
|
+
full_path = os.path.abspath(os.path.expanduser(file_path))
|
|
160
|
+
|
|
161
|
+
if not os.path.exists(full_path):
|
|
162
|
+
return f"文件不存在:{full_path}"
|
|
163
|
+
|
|
164
|
+
prs = Presentation(full_path)
|
|
165
|
+
output = [f"📊 PPT文件:{os.path.basename(full_path)}"]
|
|
166
|
+
output.append(f"📄 共 {len(prs.slides)} 页\n")
|
|
167
|
+
|
|
168
|
+
for i, slide in enumerate(prs.slides, 1):
|
|
169
|
+
slide_texts = []
|
|
170
|
+
for shape in slide.shapes:
|
|
171
|
+
if hasattr(shape, "text") and shape.text.strip():
|
|
172
|
+
slide_texts.append(shape.text.strip())
|
|
173
|
+
|
|
174
|
+
output.append(f"\n【第 {i} 页】")
|
|
175
|
+
if slide_texts:
|
|
176
|
+
output.append("\n".join(slide_texts))
|
|
177
|
+
else:
|
|
178
|
+
output.append("(无文字内容)")
|
|
179
|
+
|
|
180
|
+
content = "\n".join(output)
|
|
181
|
+
if len(content) > 8000:
|
|
182
|
+
content = content[:8000] + "\n\n...(内容过长,已截断)"
|
|
183
|
+
|
|
184
|
+
return content
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return f"读取PPT失败:{e}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@tool
|
|
191
|
+
def list_files(directory: str) -> str:
|
|
192
|
+
"""列出指定目录下的所有文件和文件夹。"""
|
|
193
|
+
try:
|
|
194
|
+
full_path = os.path.abspath(os.path.expanduser(directory))
|
|
195
|
+
|
|
196
|
+
if not os.path.exists(full_path):
|
|
197
|
+
return f"目录不存在:{full_path}"
|
|
198
|
+
|
|
199
|
+
items = os.listdir(full_path)
|
|
200
|
+
if not items:
|
|
201
|
+
return "目录为空"
|
|
202
|
+
|
|
203
|
+
output = []
|
|
204
|
+
for item in sorted(items):
|
|
205
|
+
item_path = os.path.join(full_path, item)
|
|
206
|
+
prefix = "📁" if os.path.isdir(item_path) else "📄"
|
|
207
|
+
output.append(f"{prefix} {item}")
|
|
208
|
+
|
|
209
|
+
return "\n".join(output)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
return f"查看失败:{e}"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_document_tools():
|
|
215
|
+
"""获取文档工具列表"""
|
|
216
|
+
return [read_file, read_pdf, read_docx, read_excel, read_pptx, list_files]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
搜索工具:联网搜索、网页抓取
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from langchain_core.tools import tool
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@tool
|
|
10
|
+
def web_search(query: str) -> str:
|
|
11
|
+
"""搜索互联网获取最新信息。传入搜索关键词。"""
|
|
12
|
+
try:
|
|
13
|
+
from ddgs import DDGS
|
|
14
|
+
|
|
15
|
+
with DDGS() as ddgs:
|
|
16
|
+
results = list(ddgs.text(query, max_results=5))
|
|
17
|
+
|
|
18
|
+
if not results:
|
|
19
|
+
return f"未找到关于 '{query}' 的结果"
|
|
20
|
+
|
|
21
|
+
output = []
|
|
22
|
+
for i, r in enumerate(results, 1):
|
|
23
|
+
output.append(
|
|
24
|
+
f"{i}. {r['title']}\n 链接:{r['href']}\n 摘要:{r['body'][:150]}..."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return "\n\n".join(output)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return f"搜索失败:{e}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@tool
|
|
33
|
+
def read_url(url: str) -> str:
|
|
34
|
+
"""抓取网页正文内容。传入完整的URL地址。"""
|
|
35
|
+
try:
|
|
36
|
+
from bs4 import BeautifulSoup
|
|
37
|
+
|
|
38
|
+
headers = {
|
|
39
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
40
|
+
}
|
|
41
|
+
response = requests.get(url, headers=headers, timeout=15)
|
|
42
|
+
response.encoding = response.apparent_encoding
|
|
43
|
+
|
|
44
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
|
45
|
+
|
|
46
|
+
# 移除脚本和样式
|
|
47
|
+
for tag in soup(["script", "style", "nav", "header", "footer", "aside"]):
|
|
48
|
+
tag.decompose()
|
|
49
|
+
|
|
50
|
+
# 获取标题
|
|
51
|
+
title = soup.title.string if soup.title else "无标题"
|
|
52
|
+
|
|
53
|
+
# 获取正文
|
|
54
|
+
main_content = soup.find("article") or soup.find("main") or soup.find("body")
|
|
55
|
+
|
|
56
|
+
if main_content:
|
|
57
|
+
paragraphs = main_content.find_all(["p", "h1", "h2", "h3", "h4", "li"])
|
|
58
|
+
text_parts = []
|
|
59
|
+
for p in paragraphs:
|
|
60
|
+
text = p.get_text(strip=True)
|
|
61
|
+
if len(text) > 20:
|
|
62
|
+
text_parts.append(text)
|
|
63
|
+
content = "\n\n".join(text_parts)
|
|
64
|
+
else:
|
|
65
|
+
content = soup.get_text(separator="\n", strip=True)
|
|
66
|
+
|
|
67
|
+
if len(content) > 5000:
|
|
68
|
+
content = content[:5000] + "\n\n...(内容过长,已截断)"
|
|
69
|
+
|
|
70
|
+
return f"📄 标题:{title}\n🔗 来源:{url}\n\n{content}"
|
|
71
|
+
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return f"抓取网页失败:{e}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_search_tools():
|
|
77
|
+
"""获取搜索工具列表"""
|
|
78
|
+
return [web_search, read_url]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gradio Web 界面
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
from consultant_agent.web import launch_web
|
|
6
|
+
launch_web(port=7860)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def launch_web(port: int = 7860, share: bool = False):
|
|
13
|
+
"""启动 Gradio Web 界面"""
|
|
14
|
+
try:
|
|
15
|
+
import gradio as gr
|
|
16
|
+
except ImportError:
|
|
17
|
+
raise ImportError("需要安装 gradio:pip install consultant-agent[web]")
|
|
18
|
+
|
|
19
|
+
from consultant_agent import ConsultantAgent
|
|
20
|
+
|
|
21
|
+
# 创建 Agent
|
|
22
|
+
api_key = os.environ.get("DEEPSEEK_API_KEY")
|
|
23
|
+
if not api_key:
|
|
24
|
+
raise ValueError("请设置环境变量 DEEPSEEK_API_KEY")
|
|
25
|
+
|
|
26
|
+
agent = ConsultantAgent(api_key=api_key, memory_file=None, enable_memory=False)
|
|
27
|
+
|
|
28
|
+
def chat(message: str, history: list) -> str:
|
|
29
|
+
"""Gradio 聊天函数"""
|
|
30
|
+
if not message.strip():
|
|
31
|
+
return ""
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# 使用历史构建上下文(简化版,不持久化)
|
|
35
|
+
return agent.ask(message)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return f"出错了:{e}"
|
|
38
|
+
|
|
39
|
+
# 示例问题
|
|
40
|
+
examples = [
|
|
41
|
+
"北京今天天气怎么样?",
|
|
42
|
+
"计算 123 * 456 + 789",
|
|
43
|
+
"搜索一下 2026年AI Agent发展趋势",
|
|
44
|
+
"帮我生成一个「新能源汽车行业分析」的报告大纲",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# 创建界面
|
|
48
|
+
with gr.Blocks(title="咨询顾问 Agent", theme=gr.themes.Soft()) as demo:
|
|
49
|
+
gr.Markdown("""
|
|
50
|
+
# 🤖 咨询顾问 Agent
|
|
51
|
+
|
|
52
|
+
**能力**:天气查询 | 数学计算 | 联网搜索 | 文档阅读 | 内容总结 | 报告大纲 | 竞品对比
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
""")
|
|
56
|
+
|
|
57
|
+
chatbot = gr.Chatbot(height=500, show_label=False)
|
|
58
|
+
|
|
59
|
+
with gr.Row():
|
|
60
|
+
msg = gr.Textbox(
|
|
61
|
+
placeholder="输入你的问题...",
|
|
62
|
+
show_label=False,
|
|
63
|
+
scale=9,
|
|
64
|
+
)
|
|
65
|
+
submit_btn = gr.Button("发送", scale=1, variant="primary")
|
|
66
|
+
|
|
67
|
+
with gr.Row():
|
|
68
|
+
clear_btn = gr.Button("🗑️ 清空对话")
|
|
69
|
+
|
|
70
|
+
gr.Examples(examples=examples, inputs=msg, label="💡 试试这些问题")
|
|
71
|
+
|
|
72
|
+
# 绑定事件
|
|
73
|
+
msg.submit(chat, [msg, chatbot], [chatbot]).then(lambda: "", None, msg)
|
|
74
|
+
submit_btn.click(chat, [msg, chatbot], [chatbot]).then(lambda: "", None, msg)
|
|
75
|
+
clear_btn.click(lambda: ([], ""), None, [chatbot, msg])
|
|
76
|
+
|
|
77
|
+
print("=" * 50)
|
|
78
|
+
print("🚀 咨询顾问 Agent Web 服务")
|
|
79
|
+
print("=" * 50)
|
|
80
|
+
print(f"访问地址:http://localhost:{port}")
|
|
81
|
+
if share:
|
|
82
|
+
print("正在生成公网链接...")
|
|
83
|
+
print("按 Ctrl+C 停止服务")
|
|
84
|
+
print("=" * 50)
|
|
85
|
+
|
|
86
|
+
demo.launch(server_name="0.0.0.0", server_port=port, share=share)
|