update web log
Browse files- owl/nextwebapp.py +813 -0
- owl/webapp_zh.py +223 -0
owl/nextwebapp.py
ADDED
@@ -0,0 +1,813 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Import from the correct module path
|
2 |
+
from owl.utils import run_society
|
3 |
+
import os
|
4 |
+
import gradio as gr
|
5 |
+
import time
|
6 |
+
import json
|
7 |
+
from typing import Tuple, List, Dict, Any
|
8 |
+
import importlib
|
9 |
+
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
|
10 |
+
|
11 |
+
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
12 |
+
|
13 |
+
# Enhanced CSS with navigation bar and additional styling
|
14 |
+
custom_css = """
|
15 |
+
:root {
|
16 |
+
--primary-color: #4a89dc;
|
17 |
+
--secondary-color: #5d9cec;
|
18 |
+
--accent-color: #7baaf7;
|
19 |
+
--light-bg: #f8f9fa;
|
20 |
+
--border-color: #e4e9f0;
|
21 |
+
--text-muted: #8a9aae;
|
22 |
+
}
|
23 |
+
|
24 |
+
.container {
|
25 |
+
max-width: 1200px;
|
26 |
+
margin: 0 auto;
|
27 |
+
}
|
28 |
+
|
29 |
+
.navbar {
|
30 |
+
display: flex;
|
31 |
+
justify-content: space-between;
|
32 |
+
align-items: center;
|
33 |
+
padding: 15px 30px;
|
34 |
+
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
35 |
+
color: white;
|
36 |
+
border-radius: 10px 10px 0 0;
|
37 |
+
margin-bottom: 0;
|
38 |
+
box-shadow: 0 2px 10px rgba(74, 137, 220, 0.15);
|
39 |
+
}
|
40 |
+
|
41 |
+
.navbar-logo {
|
42 |
+
display: flex;
|
43 |
+
align-items: center;
|
44 |
+
gap: 10px;
|
45 |
+
font-size: 1.5em;
|
46 |
+
font-weight: bold;
|
47 |
+
}
|
48 |
+
|
49 |
+
.navbar-menu {
|
50 |
+
display: flex;
|
51 |
+
gap: 20px;
|
52 |
+
}
|
53 |
+
|
54 |
+
/* Navbar styles moved to a more specific section below */
|
55 |
+
|
56 |
+
.header {
|
57 |
+
text-align: center;
|
58 |
+
margin-bottom: 20px;
|
59 |
+
background: linear-gradient(180deg, var(--secondary-color), var(--accent-color));
|
60 |
+
color: white;
|
61 |
+
padding: 40px 20px;
|
62 |
+
border-radius: 0 0 10px 10px;
|
63 |
+
box-shadow: 0 4px 6px rgba(93, 156, 236, 0.12);
|
64 |
+
}
|
65 |
+
|
66 |
+
.module-info {
|
67 |
+
background-color: var(--light-bg);
|
68 |
+
border-left: 5px solid var(--primary-color);
|
69 |
+
padding: 10px 15px;
|
70 |
+
margin-top: 10px;
|
71 |
+
border-radius: 5px;
|
72 |
+
font-size: 0.9em;
|
73 |
+
}
|
74 |
+
|
75 |
+
.answer-box {
|
76 |
+
background-color: var(--light-bg);
|
77 |
+
border-left: 5px solid var(--secondary-color);
|
78 |
+
padding: 15px;
|
79 |
+
margin-bottom: 20px;
|
80 |
+
border-radius: 5px;
|
81 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
82 |
+
}
|
83 |
+
|
84 |
+
.token-count {
|
85 |
+
background-color: #e9ecef;
|
86 |
+
padding: 10px;
|
87 |
+
border-radius: 5px;
|
88 |
+
text-align: center;
|
89 |
+
font-weight: bold;
|
90 |
+
margin-bottom: 20px;
|
91 |
+
}
|
92 |
+
|
93 |
+
.chat-container {
|
94 |
+
border: 1px solid var(--border-color);
|
95 |
+
border-radius: 5px;
|
96 |
+
max-height: 500px;
|
97 |
+
overflow-y: auto;
|
98 |
+
margin-bottom: 20px;
|
99 |
+
}
|
100 |
+
|
101 |
+
.footer {
|
102 |
+
text-align: center;
|
103 |
+
margin-top: 20px;
|
104 |
+
color: var(--text-muted);
|
105 |
+
font-size: 0.9em;
|
106 |
+
padding: 20px;
|
107 |
+
border-top: 1px solid var(--border-color);
|
108 |
+
}
|
109 |
+
|
110 |
+
.features-section {
|
111 |
+
display: grid;
|
112 |
+
grid-template-columns: repeat(3, 1fr);
|
113 |
+
gap: 20px;
|
114 |
+
margin: 20px 0;
|
115 |
+
}
|
116 |
+
|
117 |
+
@media (max-width: 1200px) {
|
118 |
+
.features-section {
|
119 |
+
grid-template-columns: repeat(2, 1fr);
|
120 |
+
}
|
121 |
+
}
|
122 |
+
|
123 |
+
@media (max-width: 768px) {
|
124 |
+
.features-section {
|
125 |
+
grid-template-columns: 1fr;
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
.feature-card {
|
130 |
+
background-color: white;
|
131 |
+
border-radius: 8px;
|
132 |
+
padding: 20px;
|
133 |
+
box-shadow: 0 2px 8px rgba(74, 137, 220, 0.08);
|
134 |
+
transition: transform 0.3s, box-shadow 0.3s;
|
135 |
+
height: 100%;
|
136 |
+
display: flex;
|
137 |
+
flex-direction: column;
|
138 |
+
border: 1px solid rgba(228, 233, 240, 0.6);
|
139 |
+
}
|
140 |
+
|
141 |
+
.feature-card:hover {
|
142 |
+
transform: translateY(-5px);
|
143 |
+
box-shadow: 0 5px 15px rgba(74, 137, 220, 0.15);
|
144 |
+
border-color: rgba(93, 156, 236, 0.3);
|
145 |
+
}
|
146 |
+
|
147 |
+
.feature-icon {
|
148 |
+
font-size: 2em;
|
149 |
+
color: var(--primary-color);
|
150 |
+
margin-bottom: 10px;
|
151 |
+
text-shadow: 0 1px 2px rgba(74, 137, 220, 0.1);
|
152 |
+
}
|
153 |
+
|
154 |
+
.feature-card h3 {
|
155 |
+
margin-top: 10px;
|
156 |
+
margin-bottom: 10px;
|
157 |
+
}
|
158 |
+
|
159 |
+
.feature-card p {
|
160 |
+
flex-grow: 1;
|
161 |
+
font-size: 0.95em;
|
162 |
+
line-height: 1.5;
|
163 |
+
}
|
164 |
+
|
165 |
+
/* Navbar link styles - ensuring consistent colors */
|
166 |
+
.navbar-menu a {
|
167 |
+
color: #ffffff !important;
|
168 |
+
text-decoration: none;
|
169 |
+
padding: 5px 10px;
|
170 |
+
border-radius: 5px;
|
171 |
+
transition: background-color 0.3s, color 0.3s;
|
172 |
+
font-weight: 500;
|
173 |
+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
174 |
+
}
|
175 |
+
|
176 |
+
.navbar-menu a:hover {
|
177 |
+
background-color: rgba(255, 255, 255, 0.15);
|
178 |
+
color: #ffffff !important;
|
179 |
+
}
|
180 |
+
|
181 |
+
/* Improved button and input styles */
|
182 |
+
button.primary {
|
183 |
+
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
184 |
+
transition: all 0.3s;
|
185 |
+
}
|
186 |
+
|
187 |
+
button.primary:hover {
|
188 |
+
background: linear-gradient(90deg, var(--secondary-color), var(--primary-color));
|
189 |
+
transform: translateY(-2px);
|
190 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
191 |
+
}
|
192 |
+
|
193 |
+
.env-section {
|
194 |
+
background-color: var(--light-bg);
|
195 |
+
border-radius: 8px;
|
196 |
+
padding: 20px;
|
197 |
+
margin: 20px 0;
|
198 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
199 |
+
}
|
200 |
+
|
201 |
+
.env-table {
|
202 |
+
width: 100%;
|
203 |
+
border-collapse: collapse;
|
204 |
+
margin-top: 15px;
|
205 |
+
}
|
206 |
+
|
207 |
+
.env-table th, .env-table td {
|
208 |
+
padding: 10px;
|
209 |
+
border: 1px solid var(--border-color);
|
210 |
+
}
|
211 |
+
|
212 |
+
.env-table th {
|
213 |
+
background-color: var(--primary-color);
|
214 |
+
color: white;
|
215 |
+
text-align: left;
|
216 |
+
}
|
217 |
+
|
218 |
+
.env-table tr:nth-child(even) {
|
219 |
+
background-color: rgba(0, 0, 0, 0.02);
|
220 |
+
}
|
221 |
+
|
222 |
+
.env-actions {
|
223 |
+
display: flex;
|
224 |
+
gap: 10px;
|
225 |
+
}
|
226 |
+
|
227 |
+
.env-var-input {
|
228 |
+
margin-bottom: 15px;
|
229 |
+
}
|
230 |
+
|
231 |
+
.env-save-status {
|
232 |
+
margin-top: 15px;
|
233 |
+
padding: 10px;
|
234 |
+
border-radius: 5px;
|
235 |
+
}
|
236 |
+
|
237 |
+
.success {
|
238 |
+
background-color: #d4edda;
|
239 |
+
color: #155724;
|
240 |
+
border: 1px solid #c3e6cb;
|
241 |
+
}
|
242 |
+
|
243 |
+
.error {
|
244 |
+
background-color: #f8d7da;
|
245 |
+
color: #721c24;
|
246 |
+
border: 1px solid #f5c6cb;
|
247 |
+
}
|
248 |
+
"""
|
249 |
+
|
250 |
+
# Dictionary containing module descriptions
|
251 |
+
MODULE_DESCRIPTIONS = {
|
252 |
+
"run": "默认模式:使用OpenAI模型的默认的智能体协作模式,适合大多数任务。",
|
253 |
+
"run_mini":"使用使用OpenAI模型最小化配置处理任务",
|
254 |
+
"run_deepseek_zh":"使用deepseek模型处理中文任务",
|
255 |
+
"run_terminal_zh": "终端模式:可执行命令行操作,支持网络搜索、文件处理等功能。适合需要系统交互的任务,使用OpenAI模型",
|
256 |
+
"run_gaia_roleplaying":"GAIA基准测试实现,用于评估Agent能力",
|
257 |
+
"run_openai_compatiable_model":"使用openai兼容模型处理任务",
|
258 |
+
"run_ollama":"使用本地ollama模型处理任务",
|
259 |
+
"run_qwen_mini_zh":"使用qwen模型最小化配置处理任务",
|
260 |
+
"run_qwen_zh":"使用qwen模型处理任务",
|
261 |
+
}
|
262 |
+
|
263 |
+
# 默认环境变量模板
|
264 |
+
DEFAULT_ENV_TEMPLATE = """# MODEL & API (See https://docs.camel-ai.org/key_modules/models.html#)
|
265 |
+
|
266 |
+
# OPENAI API
|
267 |
+
# OPENAI_API_KEY= ""
|
268 |
+
# OPENAI_API_BASE_URL=""
|
269 |
+
|
270 |
+
# Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
|
271 |
+
# QWEN_API_KEY=""
|
272 |
+
|
273 |
+
# DeepSeek API (https://platform.deepseek.com/api_keys)
|
274 |
+
# DEEPSEEK_API_KEY=""
|
275 |
+
|
276 |
+
#===========================================
|
277 |
+
# Tools & Services API
|
278 |
+
#===========================================
|
279 |
+
|
280 |
+
# Google Search API (https://developers.google.com/custom-search/v1/overview)
|
281 |
+
GOOGLE_API_KEY=""
|
282 |
+
SEARCH_ENGINE_ID=""
|
283 |
+
|
284 |
+
# Hugging Face API (https://huggingface.co/join)
|
285 |
+
HF_TOKEN=""
|
286 |
+
|
287 |
+
# Chunkr API (https://chunkr.ai/)
|
288 |
+
CHUNKR_API_KEY=""
|
289 |
+
|
290 |
+
# Firecrawl API (https://www.firecrawl.dev/)
|
291 |
+
FIRECRAWL_API_KEY=""
|
292 |
+
#FIRECRAWL_API_URL="https://api.firecrawl.dev"
|
293 |
+
"""
|
294 |
+
|
295 |
+
def format_chat_history(chat_history: List[Dict[str, str]]) -> List[List[str]]:
|
296 |
+
"""将聊天历史格式化为Gradio聊天组件可接受的格式
|
297 |
+
|
298 |
+
Args:
|
299 |
+
chat_history: 原始聊天历史
|
300 |
+
|
301 |
+
Returns:
|
302 |
+
List[List[str]]: 格式化后的聊天历史
|
303 |
+
"""
|
304 |
+
formatted_history = []
|
305 |
+
for message in chat_history:
|
306 |
+
user_msg = message.get("user", "")
|
307 |
+
assistant_msg = message.get("assistant", "")
|
308 |
+
|
309 |
+
if user_msg:
|
310 |
+
formatted_history.append([user_msg, None])
|
311 |
+
if assistant_msg and formatted_history:
|
312 |
+
formatted_history[-1][1] = assistant_msg
|
313 |
+
elif assistant_msg:
|
314 |
+
formatted_history.append([None, assistant_msg])
|
315 |
+
|
316 |
+
return formatted_history
|
317 |
+
|
318 |
+
def validate_input(question: str) -> bool:
|
319 |
+
"""验证用户输入是否有效
|
320 |
+
|
321 |
+
Args:
|
322 |
+
question: 用户问题
|
323 |
+
|
324 |
+
Returns:
|
325 |
+
bool: 输入是否有效
|
326 |
+
"""
|
327 |
+
# 检查输入是否为空或只包含空格
|
328 |
+
if not question or question.strip() == "":
|
329 |
+
return False
|
330 |
+
return True
|
331 |
+
|
332 |
+
def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], str, str]:
|
333 |
+
"""运行OWL系统并返回结果
|
334 |
+
|
335 |
+
Args:
|
336 |
+
question: 用户问题
|
337 |
+
example_module: 要导入的示例模块名(如 "run_terminal_zh" 或 "run_deep")
|
338 |
+
|
339 |
+
Returns:
|
340 |
+
Tuple[...]: 回答、聊天历史、令牌计数、状态
|
341 |
+
"""
|
342 |
+
# 验证输入
|
343 |
+
if not validate_input(question):
|
344 |
+
return (
|
345 |
+
"请输入有效的问题",
|
346 |
+
[],
|
347 |
+
"0",
|
348 |
+
"❌ 错误: 输入无效"
|
349 |
+
)
|
350 |
+
|
351 |
+
try:
|
352 |
+
# 确保环境变量已加载
|
353 |
+
load_dotenv(find_dotenv(), override=True)
|
354 |
+
# 检查模块是否在MODULE_DESCRIPTIONS中
|
355 |
+
if example_module not in MODULE_DESCRIPTIONS:
|
356 |
+
return (
|
357 |
+
f"所选模块 '{example_module}' 不受支持",
|
358 |
+
[],
|
359 |
+
"0",
|
360 |
+
f"❌ 错误: 不支持的模块"
|
361 |
+
)
|
362 |
+
|
363 |
+
# 动态导入目标模块
|
364 |
+
module_path = f"owl.examples.{example_module}"
|
365 |
+
try:
|
366 |
+
module = importlib.import_module(module_path)
|
367 |
+
except ImportError as ie:
|
368 |
+
return (
|
369 |
+
f"无法导入模块: {module_path}",
|
370 |
+
[],
|
371 |
+
"0",
|
372 |
+
f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
|
373 |
+
)
|
374 |
+
except Exception as e:
|
375 |
+
return (
|
376 |
+
f"导入模块时发生错误: {module_path}",
|
377 |
+
[],
|
378 |
+
"0",
|
379 |
+
f"❌ 错误: {str(e)}"
|
380 |
+
)
|
381 |
+
|
382 |
+
# 检查是否包含construct_society函数
|
383 |
+
if not hasattr(module, "construct_society"):
|
384 |
+
return (
|
385 |
+
f"模块 {module_path} 中未找到 construct_society 函数",
|
386 |
+
[],
|
387 |
+
"0",
|
388 |
+
f"❌ 错误: 模块接口不兼容"
|
389 |
+
)
|
390 |
+
|
391 |
+
# 构建社会模��
|
392 |
+
try:
|
393 |
+
society = module.construct_society(question)
|
394 |
+
except Exception as e:
|
395 |
+
return (
|
396 |
+
f"构建社会模拟时发生错误: {str(e)}",
|
397 |
+
[],
|
398 |
+
"0",
|
399 |
+
f"❌ 错误: 构建失败 - {str(e)}"
|
400 |
+
)
|
401 |
+
|
402 |
+
# 运行社会模拟
|
403 |
+
try:
|
404 |
+
answer, chat_history, token_info = run_society(society)
|
405 |
+
except Exception as e:
|
406 |
+
return (
|
407 |
+
f"运行社会模拟时发生错误: {str(e)}",
|
408 |
+
[],
|
409 |
+
"0",
|
410 |
+
f"❌ 错误: 运行失败 - {str(e)}"
|
411 |
+
)
|
412 |
+
|
413 |
+
# 格式化聊天历史
|
414 |
+
try:
|
415 |
+
formatted_chat_history = format_chat_history(chat_history)
|
416 |
+
except Exception as e:
|
417 |
+
# 如果格式化失败,返回空历史记录但继续处理
|
418 |
+
formatted_chat_history = []
|
419 |
+
|
420 |
+
# 安全地获取令牌计数
|
421 |
+
if not isinstance(token_info, dict):
|
422 |
+
token_info = {}
|
423 |
+
|
424 |
+
completion_tokens = token_info.get("completion_token_count", 0)
|
425 |
+
prompt_tokens = token_info.get("prompt_token_count", 0)
|
426 |
+
total_tokens = completion_tokens + prompt_tokens
|
427 |
+
|
428 |
+
return (
|
429 |
+
answer,
|
430 |
+
formatted_chat_history,
|
431 |
+
f"完成令牌: {completion_tokens:,} | 提示令牌: {prompt_tokens:,} | 总计: {total_tokens:,}",
|
432 |
+
"✅ 成功完成"
|
433 |
+
)
|
434 |
+
|
435 |
+
except Exception as e:
|
436 |
+
return (
|
437 |
+
f"发生错误: {str(e)}",
|
438 |
+
[],
|
439 |
+
"0",
|
440 |
+
f"❌ 错误: {str(e)}"
|
441 |
+
)
|
442 |
+
|
443 |
+
def update_module_description(module_name: str) -> str:
|
444 |
+
"""返回所选模块的描述"""
|
445 |
+
return MODULE_DESCRIPTIONS.get(module_name, "无可用描述")
|
446 |
+
|
447 |
+
# 环境变量管理功能
|
448 |
+
def init_env_file():
|
449 |
+
"""初始化.env文件如果不存在"""
|
450 |
+
dotenv_path = find_dotenv()
|
451 |
+
if not dotenv_path:
|
452 |
+
with open(".env", "w") as f:
|
453 |
+
f.write(DEFAULT_ENV_TEMPLATE)
|
454 |
+
dotenv_path = find_dotenv()
|
455 |
+
return dotenv_path
|
456 |
+
|
457 |
+
def load_env_vars():
|
458 |
+
"""加载环境变量并返回字典格式"""
|
459 |
+
dotenv_path = init_env_file()
|
460 |
+
load_dotenv(dotenv_path, override=True)
|
461 |
+
|
462 |
+
env_vars = {}
|
463 |
+
with open(dotenv_path, "r") as f:
|
464 |
+
for line in f:
|
465 |
+
line = line.strip()
|
466 |
+
if line and not line.startswith("#"):
|
467 |
+
if "=" in line:
|
468 |
+
key, value = line.split("=", 1)
|
469 |
+
env_vars[key.strip()] = value.strip().strip('"\'')
|
470 |
+
|
471 |
+
return env_vars
|
472 |
+
|
473 |
+
def save_env_vars(env_vars):
|
474 |
+
"""保存环境变量到.env文件"""
|
475 |
+
try:
|
476 |
+
dotenv_path = init_env_file()
|
477 |
+
|
478 |
+
# 保存每个环境变量
|
479 |
+
for key, value in env_vars.items():
|
480 |
+
if key and key.strip(): # 确保键不为空
|
481 |
+
set_key(dotenv_path, key.strip(), value.strip())
|
482 |
+
|
483 |
+
# 重新加载环境变量以确保生效
|
484 |
+
load_dotenv(dotenv_path, override=True)
|
485 |
+
|
486 |
+
return True, "环境变量已成功保存!"
|
487 |
+
except Exception as e:
|
488 |
+
return False, f"保存环境变量时出错: {str(e)}"
|
489 |
+
|
490 |
+
def add_env_var(key, value):
|
491 |
+
"""添加或更新单个环境变量"""
|
492 |
+
try:
|
493 |
+
if not key or not key.strip():
|
494 |
+
return False, "变量名不能为空"
|
495 |
+
|
496 |
+
dotenv_path = init_env_file()
|
497 |
+
set_key(dotenv_path, key.strip(), value.strip())
|
498 |
+
load_dotenv(dotenv_path, override=True)
|
499 |
+
|
500 |
+
return True, f"环境变量 {key} 已成功添加/更新!"
|
501 |
+
except Exception as e:
|
502 |
+
return False, f"添加环境变量时出错: {str(e)}"
|
503 |
+
|
504 |
+
def delete_env_var(key):
|
505 |
+
"""删除环境变量"""
|
506 |
+
try:
|
507 |
+
if not key or not key.strip():
|
508 |
+
return False, "变量名不能为空"
|
509 |
+
|
510 |
+
dotenv_path = init_env_file()
|
511 |
+
unset_key(dotenv_path, key.strip())
|
512 |
+
|
513 |
+
# 从当前进程环境中也删除
|
514 |
+
if key in os.environ:
|
515 |
+
del os.environ[key]
|
516 |
+
|
517 |
+
return True, f"环境变量 {key} 已成功删除!"
|
518 |
+
except Exception as e:
|
519 |
+
return False, f"删除环境变量时出错: {str(e)}"
|
520 |
+
|
521 |
+
def mask_sensitive_value(key: str, value: str) -> str:
|
522 |
+
"""对敏感信息进行掩码处理
|
523 |
+
|
524 |
+
Args:
|
525 |
+
key: 环境变量名
|
526 |
+
value: 环境变量值
|
527 |
+
|
528 |
+
Returns:
|
529 |
+
str: 处理后的值
|
530 |
+
"""
|
531 |
+
# 定义需要掩码的敏感关键词
|
532 |
+
sensitive_keywords = ['key', 'token', 'secret', 'password', 'api']
|
533 |
+
|
534 |
+
# 检查是否包含敏感关键词(不区分大小写)
|
535 |
+
is_sensitive = any(keyword in key.lower() for keyword in sensitive_keywords)
|
536 |
+
|
537 |
+
if is_sensitive and value:
|
538 |
+
# 如果是敏感信息且有值,则显示掩码
|
539 |
+
return '*' * 8
|
540 |
+
return value
|
541 |
+
|
542 |
+
def update_env_table():
|
543 |
+
"""更新环境变量表格显示,对敏感信息进行掩码处理"""
|
544 |
+
env_vars = load_env_vars()
|
545 |
+
# 对敏感值进行掩码处理
|
546 |
+
masked_env_vars = [[k, mask_sensitive_value(k, v)] for k, v in env_vars.items()]
|
547 |
+
return masked_env_vars
|
548 |
+
|
549 |
+
def create_ui():
|
550 |
+
"""创建增强版Gradio界面"""
|
551 |
+
with gr.Blocks(css=custom_css, theme=gr.themes.Soft(primary_hue="blue")) as app:
|
552 |
+
with gr.Column(elem_classes="container"):
|
553 |
+
gr.HTML("""
|
554 |
+
<div class="navbar">
|
555 |
+
<div class="navbar-logo">
|
556 |
+
🦉 OWL 多智能体协作系统
|
557 |
+
</div>
|
558 |
+
<div class="navbar-menu">
|
559 |
+
<a href="#home">首页</a>
|
560 |
+
<a href="#env-settings">环境设置</a>
|
561 |
+
<a href="https://github.com/camel-ai/owl/blob/main/README.md#-community">加入交流群</a>
|
562 |
+
<a href="https://github.com/camel-ai/owl/blob/main/README.md">OWL文档</a>
|
563 |
+
<a href="https://github.com/camel-ai/camel">CAMEL框架</a>
|
564 |
+
<a href="https://camel-ai.org">CAMEL-AI官网</a>
|
565 |
+
</div>
|
566 |
+
</div>
|
567 |
+
<div class="header" id="home">
|
568 |
+
|
569 |
+
<p>我们的愿景是彻底改变AI代理协作解决现实世界任务的方式。通过利用动态代理交互,OWL能够在多个领域实现更自然、高效和稳健的任务自动化。</p>
|
570 |
+
</div>
|
571 |
+
""")
|
572 |
+
|
573 |
+
with gr.Row(elem_id="features"):
|
574 |
+
gr.HTML("""
|
575 |
+
<div class="features-section">
|
576 |
+
<div class="feature-card">
|
577 |
+
<div class="feature-icon">🔍</div>
|
578 |
+
<h3>实时信息检索</h3>
|
579 |
+
<p>利用维基百科、谷歌搜索和其他在线资源获取最新信息。</p>
|
580 |
+
</div>
|
581 |
+
<div class="feature-card">
|
582 |
+
<div class="feature-icon">📹</div>
|
583 |
+
<h3>多模态处理</h3>
|
584 |
+
<p>支持处理互联网或本地的视频、图像和音频数据。</p>
|
585 |
+
</div>
|
586 |
+
<div class="feature-card">
|
587 |
+
<div class="feature-icon">🌐</div>
|
588 |
+
<h3>浏览器自动化</h3>
|
589 |
+
<p>使用Playwright框架模拟浏览器交互,实现网页操作自动化。</p>
|
590 |
+
</div>
|
591 |
+
<div class="feature-card">
|
592 |
+
<div class="feature-icon">📄</div>
|
593 |
+
<h3>文档解析</h3>
|
594 |
+
<p>从各种文档格式中提取内容,并转换为易于处理的格式。</p>
|
595 |
+
</div>
|
596 |
+
<div class="feature-card">
|
597 |
+
<div class="feature-icon">💻</div>
|
598 |
+
<h3>代码执行</h3>
|
599 |
+
<p>使用解释器编写和运行Python代码,实现自动化数据处理。</p>
|
600 |
+
</div>
|
601 |
+
<div class="feature-card">
|
602 |
+
<div class="feature-icon">🧰</div>
|
603 |
+
<h3>内置工具包</h3>
|
604 |
+
<p>提供丰富的工具包,支持搜索、数据分析、代码执行等多种功能。</p>
|
605 |
+
</div>
|
606 |
+
<div class="feature-card">
|
607 |
+
<div class="feature-icon">🔑</div>
|
608 |
+
<h3>环境变量管理</h3>
|
609 |
+
<p>便捷管理API密钥和环境配置,安全存储敏感信息。</p>
|
610 |
+
</div>
|
611 |
+
</div>
|
612 |
+
""")
|
613 |
+
|
614 |
+
with gr.Row():
|
615 |
+
with gr.Column(scale=2):
|
616 |
+
question_input = gr.Textbox(
|
617 |
+
lines=5,
|
618 |
+
placeholder="请输入您的问题...",
|
619 |
+
label="问题",
|
620 |
+
elem_id="question_input",
|
621 |
+
show_copy_button=True,
|
622 |
+
)
|
623 |
+
|
624 |
+
# 增强版模块选择下拉菜单
|
625 |
+
# 只包含MODULE_DESCRIPTIONS中定义的模块
|
626 |
+
module_dropdown = gr.Dropdown(
|
627 |
+
choices=list(MODULE_DESCRIPTIONS.keys()),
|
628 |
+
value="run_terminal_zh",
|
629 |
+
label="选择功能模块",
|
630 |
+
interactive=True
|
631 |
+
)
|
632 |
+
|
633 |
+
# 模块描述文本框
|
634 |
+
module_description = gr.Textbox(
|
635 |
+
value=MODULE_DESCRIPTIONS["run_terminal_zh"],
|
636 |
+
label="模块描述",
|
637 |
+
interactive=False,
|
638 |
+
elem_classes="module-info"
|
639 |
+
)
|
640 |
+
|
641 |
+
run_button = gr.Button("运行", variant="primary", elem_classes="primary")
|
642 |
+
|
643 |
+
with gr.Column(scale=1):
|
644 |
+
gr.Markdown("""
|
645 |
+
### 使用指南
|
646 |
+
|
647 |
+
1. **选择适合的模块**:根据您的任务需求选择合适的功能模块
|
648 |
+
2. **详细描述您的需求**:在输入框中清晰描述您的问题或任务
|
649 |
+
3. **启动智能处理**:点击"运行"按钮开始多智能体协作处理
|
650 |
+
4. **查看结果**:在下方标签页查看回答和完整对话历史
|
651 |
+
|
652 |
+
> **高级提示**: 对于复杂任务,可以尝试指定具体步骤和预期结果
|
653 |
+
""")
|
654 |
+
|
655 |
+
status_output = gr.Textbox(label="状态", interactive=False)
|
656 |
+
|
657 |
+
with gr.Tabs():
|
658 |
+
with gr.TabItem("回答"):
|
659 |
+
answer_output = gr.Textbox(
|
660 |
+
label="回答",
|
661 |
+
lines=10,
|
662 |
+
elem_classes="answer-box"
|
663 |
+
)
|
664 |
+
|
665 |
+
with gr.TabItem("对话历史"):
|
666 |
+
chat_output = gr.Chatbot(
|
667 |
+
label="完整对话记录",
|
668 |
+
elem_classes="chat-container",
|
669 |
+
height=500
|
670 |
+
)
|
671 |
+
|
672 |
+
|
673 |
+
|
674 |
+
token_count_output = gr.Textbox(
|
675 |
+
label="令牌计数",
|
676 |
+
interactive=False,
|
677 |
+
elem_classes="token-count"
|
678 |
+
)
|
679 |
+
|
680 |
+
# 示例问题
|
681 |
+
examples = [
|
682 |
+
"打开百度搜索,总结一下camel-ai的camel框架的github star、fork数目等,并把数字用plot包写成python文件保存到本地,用本地终端执行python文件显示图出来给我",
|
683 |
+
"请分析GitHub上CAMEL-AI项目的最新统计数据。找出该项目的星标数量、贡献者数量和最近的活跃度。",
|
684 |
+
"浏览亚马逊并找出一款对程序员有吸引力的产品。请提供产品名称和价格",
|
685 |
+
"写一个hello world的python文件,保存到本地",
|
686 |
+
|
687 |
+
]
|
688 |
+
|
689 |
+
gr.Examples(
|
690 |
+
examples=examples,
|
691 |
+
inputs=question_input
|
692 |
+
)
|
693 |
+
# 新增: 环境变量管理选项卡
|
694 |
+
with gr.TabItem("环境变量管理", id="env-settings"):
|
695 |
+
gr.Markdown("""
|
696 |
+
## 环境变量管理
|
697 |
+
|
698 |
+
在此处设置模型API密钥和其他服务凭证。这些信息将保存在本地的`.env`文件中,确保您的API密钥安全存储且不会上传到网络。
|
699 |
+
""")
|
700 |
+
|
701 |
+
# 环境变量表格
|
702 |
+
env_table = gr.Dataframe(
|
703 |
+
headers=["变量名", "值"],
|
704 |
+
datatype=["str", "str"],
|
705 |
+
row_count=10,
|
706 |
+
col_count=(2, "fixed"),
|
707 |
+
value=update_env_table,
|
708 |
+
label="当前环境变量",
|
709 |
+
interactive=False
|
710 |
+
)
|
711 |
+
|
712 |
+
with gr.Row():
|
713 |
+
with gr.Column(scale=1):
|
714 |
+
new_env_key = gr.Textbox(label="变量名", placeholder="例如: OPENAI_API_KEY")
|
715 |
+
with gr.Column(scale=2):
|
716 |
+
new_env_value = gr.Textbox(label="值", placeholder="输入API密钥或其他配置值")
|
717 |
+
|
718 |
+
with gr.Row():
|
719 |
+
add_env_button = gr.Button("添加/更新变量", variant="primary")
|
720 |
+
refresh_button = gr.Button("刷新变量列表")
|
721 |
+
delete_env_button = gr.Button("删除选定变量", variant="stop")
|
722 |
+
|
723 |
+
env_status = gr.Textbox(label="状态", interactive=False)
|
724 |
+
|
725 |
+
# 变量选择器(用于删除)
|
726 |
+
env_var_to_delete = gr.Dropdown(
|
727 |
+
choices=[],
|
728 |
+
label="选择要删除的变量",
|
729 |
+
interactive=True
|
730 |
+
)
|
731 |
+
|
732 |
+
# 更新变量选择器的选项
|
733 |
+
def update_delete_dropdown():
|
734 |
+
env_vars = load_env_vars()
|
735 |
+
return gr.Dropdown.update(choices=list(env_vars.keys()))
|
736 |
+
|
737 |
+
# 连接事件处理函数
|
738 |
+
add_env_button.click(
|
739 |
+
fn=lambda k, v: add_env_var(k, v),
|
740 |
+
inputs=[new_env_key, new_env_value],
|
741 |
+
outputs=[env_status]
|
742 |
+
).then(
|
743 |
+
fn=update_env_table,
|
744 |
+
outputs=[env_table]
|
745 |
+
).then(
|
746 |
+
fn=update_delete_dropdown,
|
747 |
+
outputs=[env_var_to_delete]
|
748 |
+
).then(
|
749 |
+
fn=lambda: ("", ""), # 修改为返回两个空字符串的元组
|
750 |
+
outputs=[new_env_key, new_env_value]
|
751 |
+
)
|
752 |
+
|
753 |
+
refresh_button.click(
|
754 |
+
fn=update_env_table,
|
755 |
+
outputs=[env_table]
|
756 |
+
).then(
|
757 |
+
fn=update_delete_dropdown,
|
758 |
+
outputs=[env_var_to_delete]
|
759 |
+
)
|
760 |
+
|
761 |
+
delete_env_button.click(
|
762 |
+
fn=lambda k: delete_env_var(k),
|
763 |
+
inputs=[env_var_to_delete],
|
764 |
+
outputs=[env_status]
|
765 |
+
).then(
|
766 |
+
fn=update_env_table,
|
767 |
+
outputs=[env_table]
|
768 |
+
).then(
|
769 |
+
fn=update_delete_dropdown,
|
770 |
+
outputs=[env_var_to_delete]
|
771 |
+
)
|
772 |
+
|
773 |
+
|
774 |
+
|
775 |
+
gr.HTML("""
|
776 |
+
<div class="footer" id="about">
|
777 |
+
<h3>关于 OWL 多智能体协作系统</h3>
|
778 |
+
<p>OWL 是一个基于CAMEL框架开发的先进多智能体协作系统,旨在通过智能体协作解决复杂问题。</p>
|
779 |
+
<p>© 2025 CAMEL-AI.org. 基于Apache License 2.0开源协议</p>
|
780 |
+
<p><a href="https://github.com/camel-ai/owl" target="_blank">GitHub</a></p>
|
781 |
+
</div>
|
782 |
+
""")
|
783 |
+
|
784 |
+
# 设置事件处理
|
785 |
+
run_button.click(
|
786 |
+
fn=run_owl,
|
787 |
+
inputs=[question_input, module_dropdown],
|
788 |
+
outputs=[answer_output, chat_output, token_count_output, status_output]
|
789 |
+
)
|
790 |
+
|
791 |
+
# 模块选择更新描述
|
792 |
+
module_dropdown.change(
|
793 |
+
fn=update_module_description,
|
794 |
+
inputs=module_dropdown,
|
795 |
+
outputs=module_description
|
796 |
+
)
|
797 |
+
|
798 |
+
return app
|
799 |
+
|
800 |
+
# 主函数
|
801 |
+
def main():
|
802 |
+
try:
|
803 |
+
# 初始化.env文件(如果不存在)
|
804 |
+
init_env_file()
|
805 |
+
app = create_ui()
|
806 |
+
app.launch(share=False)
|
807 |
+
except Exception as e:
|
808 |
+
print(f"启动应用程序时发生错误: {str(e)}")
|
809 |
+
import traceback
|
810 |
+
traceback.print_exc()
|
811 |
+
|
812 |
+
if __name__ == "__main__":
|
813 |
+
main()
|
owl/webapp_zh.py
CHANGED
@@ -4,13 +4,131 @@ import os
|
|
4 |
import gradio as gr
|
5 |
import time
|
6 |
import json
|
|
|
|
|
7 |
from typing import Tuple, List, Dict, Any
|
8 |
import importlib
|
9 |
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
|
|
|
|
|
|
|
10 |
|
11 |
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
# Dictionary containing module descriptions
|
16 |
MODULE_DESCRIPTIONS = {
|
@@ -150,6 +268,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
150 |
"""
|
151 |
# 验证输入
|
152 |
if not validate_input(question):
|
|
|
153 |
return (
|
154 |
"请输入有效的问题",
|
155 |
[],
|
@@ -160,8 +279,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
160 |
try:
|
161 |
# 确保环境变量已加载
|
162 |
load_dotenv(find_dotenv(), override=True)
|
|
|
|
|
163 |
# 检查模块是否在MODULE_DESCRIPTIONS中
|
164 |
if example_module not in MODULE_DESCRIPTIONS:
|
|
|
165 |
return (
|
166 |
f"所选模块 '{example_module}' 不受支持",
|
167 |
[],
|
@@ -172,8 +294,10 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
172 |
# 动态导入目标模块
|
173 |
module_path = f"owl.examples.{example_module}"
|
174 |
try:
|
|
|
175 |
module = importlib.import_module(module_path)
|
176 |
except ImportError as ie:
|
|
|
177 |
return (
|
178 |
f"无法导入模块: {module_path}",
|
179 |
[],
|
@@ -181,6 +305,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
181 |
f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
|
182 |
)
|
183 |
except Exception as e:
|
|
|
184 |
return (
|
185 |
f"导入模块时发生错误: {module_path}",
|
186 |
[],
|
@@ -190,6 +315,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
190 |
|
191 |
# 检查是否包含construct_society函数
|
192 |
if not hasattr(module, "construct_society"):
|
|
|
193 |
return (
|
194 |
f"模块 {module_path} 中未找到 construct_society 函数",
|
195 |
[],
|
@@ -199,8 +325,10 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
199 |
|
200 |
# 构建社会模拟
|
201 |
try:
|
|
|
202 |
society = module.construct_society(question)
|
203 |
except Exception as e:
|
|
|
204 |
return (
|
205 |
f"构建社会模拟时发生错误: {str(e)}",
|
206 |
[],
|
@@ -210,8 +338,11 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
210 |
|
211 |
# 运行社会模拟
|
212 |
try:
|
|
|
213 |
answer, chat_history, token_info = run_society(society)
|
|
|
214 |
except Exception as e:
|
|
|
215 |
return (
|
216 |
f"运行社会模拟时发生错误: {str(e)}",
|
217 |
[],
|
@@ -224,6 +355,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
224 |
formatted_chat_history = format_chat_history(chat_history)
|
225 |
except Exception as e:
|
226 |
# 如果格式化失败,返回空历史记录但继续处理
|
|
|
227 |
formatted_chat_history = []
|
228 |
|
229 |
# 安全地获取令牌计数
|
@@ -234,6 +366,8 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
234 |
prompt_tokens = token_info.get("prompt_token_count", 0)
|
235 |
total_tokens = completion_tokens + prompt_tokens
|
236 |
|
|
|
|
|
237 |
return (
|
238 |
answer,
|
239 |
formatted_chat_history,
|
@@ -242,6 +376,7 @@ def run_owl(question: str, example_module: str) -> Tuple[str, List[List[str]], s
|
|
242 |
)
|
243 |
|
244 |
except Exception as e:
|
|
|
245 |
return (
|
246 |
f"发生错误: {str(e)}",
|
247 |
[],
|
@@ -357,6 +492,16 @@ def update_env_table():
|
|
357 |
|
358 |
def create_ui():
|
359 |
"""创建增强版Gradio界面"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
|
361 |
|
362 |
|
@@ -413,6 +558,27 @@ def create_ui():
|
|
413 |
height=500
|
414 |
)
|
415 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
416 |
with gr.TabItem("环境变量管理", id="env-settings"):
|
417 |
gr.Markdown("""
|
418 |
## 环境变量管理
|
@@ -540,6 +706,9 @@ def create_ui():
|
|
540 |
fn=run_owl,
|
541 |
inputs=[question_input, module_dropdown],
|
542 |
outputs=[answer_output, chat_output, token_count_output, status_output]
|
|
|
|
|
|
|
543 |
)
|
544 |
|
545 |
# 模块选择更新描述
|
@@ -548,20 +717,74 @@ def create_ui():
|
|
548 |
inputs=module_dropdown,
|
549 |
outputs=module_description
|
550 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
|
552 |
return app
|
553 |
|
554 |
# 主函数
|
555 |
def main():
|
556 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
557 |
# 初始化.env文件(如果不存在)
|
558 |
init_env_file()
|
559 |
app = create_ui()
|
|
|
|
|
|
|
|
|
|
|
|
|
560 |
app.launch(share=False)
|
561 |
except Exception as e:
|
|
|
562 |
print(f"启动应用程序时发生错误: {str(e)}")
|
563 |
import traceback
|
564 |
traceback.print_exc()
|
|
|
|
|
|
|
|
|
|
|
565 |
|
566 |
if __name__ == "__main__":
|
567 |
main()
|
|
|
4 |
import gradio as gr
|
5 |
import time
|
6 |
import json
|
7 |
+
import logging
|
8 |
+
import datetime
|
9 |
from typing import Tuple, List, Dict, Any
|
10 |
import importlib
|
11 |
from dotenv import load_dotenv, set_key, find_dotenv, unset_key
|
12 |
+
import threading
|
13 |
+
import queue
|
14 |
+
import time
|
15 |
|
16 |
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
17 |
|
18 |
+
# 配置日志系统
|
19 |
+
def setup_logging():
|
20 |
+
"""配置日志系统,将日志输出到文件和内存队列"""
|
21 |
+
# 创建logs目录(如果不存在)
|
22 |
+
logs_dir = os.path.join(os.path.dirname(__file__), "logs")
|
23 |
+
os.makedirs(logs_dir, exist_ok=True)
|
24 |
+
|
25 |
+
# 生成日志文件名(使用当前日期)
|
26 |
+
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
|
27 |
+
log_file = os.path.join(logs_dir, f"gradio_log_{current_date}.txt")
|
28 |
+
|
29 |
+
# 配置根日志记录器(捕获所有日志)
|
30 |
+
root_logger = logging.getLogger()
|
31 |
+
|
32 |
+
# 清除现有的处理器,避免重复日志
|
33 |
+
for handler in root_logger.handlers[:]:
|
34 |
+
root_logger.removeHandler(handler)
|
35 |
+
|
36 |
+
root_logger.setLevel(logging.INFO)
|
37 |
+
|
38 |
+
# 创建文件处理器
|
39 |
+
file_handler = logging.FileHandler(log_file, encoding='utf-8', mode='a')
|
40 |
+
file_handler.setLevel(logging.INFO)
|
41 |
+
|
42 |
+
# 创建格式化器
|
43 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
44 |
+
file_handler.setFormatter(formatter)
|
45 |
+
|
46 |
+
# 添加处理器到根日志记录器
|
47 |
+
root_logger.addHandler(file_handler)
|
48 |
+
|
49 |
+
# 配置CAMEL日志系统(如果可用)
|
50 |
+
try:
|
51 |
+
from camel.logger import set_log_file, set_log_level, enable_logging
|
52 |
+
# 启用CAMEL日志
|
53 |
+
enable_logging()
|
54 |
+
# 设置CAMEL日志文件为同一个文件
|
55 |
+
set_log_file(log_file)
|
56 |
+
# 设置日志级别
|
57 |
+
set_log_level("INFO")
|
58 |
+
logging.info("CAMEL日志系统已配置")
|
59 |
+
except ImportError:
|
60 |
+
logging.warning("无法导入CAMEL日志模块,将只使用Python标准日志")
|
61 |
+
except Exception as e:
|
62 |
+
logging.warning(f"配置CAMEL日志时出错: {str(e)}")
|
63 |
+
|
64 |
+
# 配置第三方库的日志级别
|
65 |
+
for logger_name in ['urllib3', 'requests', 'gradio']:
|
66 |
+
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
67 |
+
|
68 |
+
logging.info("日志系统已初始化,日志文件: %s", log_file)
|
69 |
+
return log_file
|
70 |
+
|
71 |
+
# 全局变量
|
72 |
+
LOG_FILE = None
|
73 |
+
LOG_QUEUE = queue.Queue()
|
74 |
+
STOP_LOG_THREAD = threading.Event()
|
75 |
|
76 |
+
# 日志读取和更新函数
|
77 |
+
def log_reader_thread(log_file):
|
78 |
+
"""后台线程,持续读取日志文件并将新行添加到队列中"""
|
79 |
+
try:
|
80 |
+
with open(log_file, 'r', encoding='utf-8') as f:
|
81 |
+
# 移动到文件末尾
|
82 |
+
f.seek(0, 2)
|
83 |
+
|
84 |
+
while not STOP_LOG_THREAD.is_set():
|
85 |
+
line = f.readline()
|
86 |
+
if line:
|
87 |
+
LOG_QUEUE.put(line)
|
88 |
+
else:
|
89 |
+
# 没有新行,等待一小段时间
|
90 |
+
time.sleep(0.1)
|
91 |
+
except Exception as e:
|
92 |
+
logging.error(f"日志读取线程出错: {str(e)}")
|
93 |
+
|
94 |
+
def get_latest_logs(max_lines=100):
|
95 |
+
"""从队列中获取最新的日志行,如果队列为空则直接从文件读取
|
96 |
+
|
97 |
+
Args:
|
98 |
+
max_lines: 最大返回行数
|
99 |
+
|
100 |
+
Returns:
|
101 |
+
str: 日志内容
|
102 |
+
"""
|
103 |
+
logs = []
|
104 |
+
try:
|
105 |
+
# 尝试从队列中获取所有可用的日志行
|
106 |
+
while not LOG_QUEUE.empty() and len(logs) < max_lines:
|
107 |
+
logs.append(LOG_QUEUE.get_nowait())
|
108 |
+
except queue.Empty:
|
109 |
+
pass
|
110 |
+
|
111 |
+
# 如果没有新日志或日志不足,尝试直接从文件读取最后几行
|
112 |
+
if len(logs) < max_lines and LOG_FILE and os.path.exists(LOG_FILE):
|
113 |
+
try:
|
114 |
+
with open(LOG_FILE, 'r', encoding='utf-8') as f:
|
115 |
+
all_lines = f.readlines()
|
116 |
+
# 如果队列中已有一些日志,只读取剩余需要的行数
|
117 |
+
remaining_lines = max_lines - len(logs)
|
118 |
+
file_logs = all_lines[-remaining_lines:] if len(all_lines) > remaining_lines else all_lines
|
119 |
+
# 将文件日志添加到队列日志之前
|
120 |
+
logs = file_logs + logs
|
121 |
+
except Exception as e:
|
122 |
+
error_msg = f"读取日志文件出错: {str(e)}"
|
123 |
+
logging.error(error_msg)
|
124 |
+
if not logs: # 只有在没有任何日志的情况下才添加错误消息
|
125 |
+
logs = [error_msg]
|
126 |
+
|
127 |
+
# 如果仍然没有日志,返回提示信息
|
128 |
+
if not logs:
|
129 |
+
return "暂无日志记录或日志系统未正确初始化。"
|
130 |
+
|
131 |
+
return "".join(logs)
|
132 |
|
133 |
# Dictionary containing module descriptions
|
134 |
MODULE_DESCRIPTIONS = {
|
|
|
268 |
"""
|
269 |
# 验证输入
|
270 |
if not validate_input(question):
|
271 |
+
logging.warning("用户提交了无效的输入")
|
272 |
return (
|
273 |
"请输入有效的问题",
|
274 |
[],
|
|
|
279 |
try:
|
280 |
# 确保环境变量已加载
|
281 |
load_dotenv(find_dotenv(), override=True)
|
282 |
+
logging.info(f"处理问题: '{question}', 使用模块: {example_module}")
|
283 |
+
|
284 |
# 检查模块是否在MODULE_DESCRIPTIONS中
|
285 |
if example_module not in MODULE_DESCRIPTIONS:
|
286 |
+
logging.error(f"用户选择了不支持的模块: {example_module}")
|
287 |
return (
|
288 |
f"所选模块 '{example_module}' 不受支持",
|
289 |
[],
|
|
|
294 |
# 动态导入目标模块
|
295 |
module_path = f"owl.examples.{example_module}"
|
296 |
try:
|
297 |
+
logging.info(f"正在导入模块: {module_path}")
|
298 |
module = importlib.import_module(module_path)
|
299 |
except ImportError as ie:
|
300 |
+
logging.error(f"无法导入模块 {module_path}: {str(ie)}")
|
301 |
return (
|
302 |
f"无法导入模块: {module_path}",
|
303 |
[],
|
|
|
305 |
f"❌ 错误: 模块 {example_module} 不存在或无法加载 - {str(ie)}"
|
306 |
)
|
307 |
except Exception as e:
|
308 |
+
logging.error(f"导入模块 {module_path} 时发生错误: {str(e)}")
|
309 |
return (
|
310 |
f"导入模块时发生错误: {module_path}",
|
311 |
[],
|
|
|
315 |
|
316 |
# 检查是否包含construct_society函数
|
317 |
if not hasattr(module, "construct_society"):
|
318 |
+
logging.error(f"模块 {module_path} 中未找到 construct_society 函数")
|
319 |
return (
|
320 |
f"模块 {module_path} 中未找到 construct_society 函数",
|
321 |
[],
|
|
|
325 |
|
326 |
# 构建社会模拟
|
327 |
try:
|
328 |
+
logging.info("正在构建社会模拟...")
|
329 |
society = module.construct_society(question)
|
330 |
except Exception as e:
|
331 |
+
logging.error(f"构建社会模拟时发生错误: {str(e)}")
|
332 |
return (
|
333 |
f"构建社会模拟时发生错误: {str(e)}",
|
334 |
[],
|
|
|
338 |
|
339 |
# 运行社会模拟
|
340 |
try:
|
341 |
+
logging.info("正在运行社会模拟...")
|
342 |
answer, chat_history, token_info = run_society(society)
|
343 |
+
logging.info("社会模拟运行完成")
|
344 |
except Exception as e:
|
345 |
+
logging.error(f"运行社会模拟时发生错误: {str(e)}")
|
346 |
return (
|
347 |
f"运行社会模拟时发生错误: {str(e)}",
|
348 |
[],
|
|
|
355 |
formatted_chat_history = format_chat_history(chat_history)
|
356 |
except Exception as e:
|
357 |
# 如果格式化失败,返回空历史记录但继续处理
|
358 |
+
logging.error(f"格式化聊天历史时发生错误: {str(e)}")
|
359 |
formatted_chat_history = []
|
360 |
|
361 |
# 安全地获取令牌计数
|
|
|
366 |
prompt_tokens = token_info.get("prompt_token_count", 0)
|
367 |
total_tokens = completion_tokens + prompt_tokens
|
368 |
|
369 |
+
logging.info(f"处理完成,令牌使用: 完成={completion_tokens}, 提示={prompt_tokens}, 总计={total_tokens}")
|
370 |
+
|
371 |
return (
|
372 |
answer,
|
373 |
formatted_chat_history,
|
|
|
376 |
)
|
377 |
|
378 |
except Exception as e:
|
379 |
+
logging.error(f"处理问题时发生未捕获的错误: {str(e)}")
|
380 |
return (
|
381 |
f"发生错误: {str(e)}",
|
382 |
[],
|
|
|
492 |
|
493 |
def create_ui():
|
494 |
"""创建增强版Gradio界面"""
|
495 |
+
|
496 |
+
# 定义日志更新函数
|
497 |
+
def update_logs():
|
498 |
+
"""获取最新日志并返回给前端显示"""
|
499 |
+
return get_latest_logs(100)
|
500 |
+
|
501 |
+
def clear_log_display():
|
502 |
+
"""清空日志显示"""
|
503 |
+
return ""
|
504 |
+
|
505 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as app:
|
506 |
|
507 |
|
|
|
558 |
height=500
|
559 |
)
|
560 |
|
561 |
+
with gr.TabItem("系统日志"):
|
562 |
+
# 添加日志显示区域
|
563 |
+
log_display = gr.Textbox(
|
564 |
+
label="系统日志",
|
565 |
+
lines=20,
|
566 |
+
max_lines=50,
|
567 |
+
interactive=False,
|
568 |
+
autoscroll=True,
|
569 |
+
show_copy_button=True,
|
570 |
+
elem_classes="log-display"
|
571 |
+
)
|
572 |
+
|
573 |
+
with gr.Row():
|
574 |
+
refresh_logs_button = gr.Button("刷新日志")
|
575 |
+
auto_refresh_checkbox = gr.Checkbox(
|
576 |
+
label="自动刷新",
|
577 |
+
value=True,
|
578 |
+
interactive=True
|
579 |
+
)
|
580 |
+
clear_logs_button = gr.Button("清空显示", variant="secondary")
|
581 |
+
|
582 |
with gr.TabItem("环境变量管理", id="env-settings"):
|
583 |
gr.Markdown("""
|
584 |
## 环境变量管理
|
|
|
706 |
fn=run_owl,
|
707 |
inputs=[question_input, module_dropdown],
|
708 |
outputs=[answer_output, chat_output, token_count_output, status_output]
|
709 |
+
).then(
|
710 |
+
fn=update_logs, # 任务完成后自动更新日志
|
711 |
+
outputs=[log_display]
|
712 |
)
|
713 |
|
714 |
# 模块选择更新描述
|
|
|
717 |
inputs=module_dropdown,
|
718 |
outputs=module_description
|
719 |
)
|
720 |
+
|
721 |
+
# 日志相关事件处理
|
722 |
+
refresh_logs_button.click(
|
723 |
+
fn=update_logs,
|
724 |
+
outputs=[log_display]
|
725 |
+
)
|
726 |
+
|
727 |
+
clear_logs_button.click(
|
728 |
+
fn=clear_log_display,
|
729 |
+
outputs=[log_display]
|
730 |
+
)
|
731 |
+
|
732 |
+
# 自动刷新控制
|
733 |
+
def toggle_auto_refresh(enabled):
|
734 |
+
if enabled:
|
735 |
+
return gr.update(every=3)
|
736 |
+
else:
|
737 |
+
return gr.update(every=0)
|
738 |
+
|
739 |
+
auto_refresh_checkbox.change(
|
740 |
+
fn=toggle_auto_refresh,
|
741 |
+
inputs=[auto_refresh_checkbox],
|
742 |
+
outputs=[log_display]
|
743 |
+
)
|
744 |
+
|
745 |
+
# 设置自动刷新(默认每3秒刷新一次)
|
746 |
+
if auto_refresh_checkbox.value:
|
747 |
+
app.load(
|
748 |
+
fn=update_logs,
|
749 |
+
outputs=[log_display],
|
750 |
+
every=3
|
751 |
+
)
|
752 |
|
753 |
return app
|
754 |
|
755 |
# 主函数
|
756 |
def main():
|
757 |
try:
|
758 |
+
# 初始化日志系统
|
759 |
+
global LOG_FILE
|
760 |
+
LOG_FILE = setup_logging()
|
761 |
+
logging.info("OWL Web应用程序启动")
|
762 |
+
|
763 |
+
# 启动日志读取线程
|
764 |
+
log_thread = threading.Thread(target=log_reader_thread, args=(LOG_FILE,), daemon=True)
|
765 |
+
log_thread.start()
|
766 |
+
logging.info("日志读取线程已启动")
|
767 |
+
|
768 |
# 初始化.env文件(如果不存在)
|
769 |
init_env_file()
|
770 |
app = create_ui()
|
771 |
+
|
772 |
+
# 注册应用关闭时的清理函数
|
773 |
+
def cleanup():
|
774 |
+
STOP_LOG_THREAD.set()
|
775 |
+
logging.info("应用程序关闭,停止日志线程")
|
776 |
+
|
777 |
app.launch(share=False)
|
778 |
except Exception as e:
|
779 |
+
logging.error(f"启动应用程序时发生错误: {str(e)}")
|
780 |
print(f"启动应用程序时发生错误: {str(e)}")
|
781 |
import traceback
|
782 |
traceback.print_exc()
|
783 |
+
|
784 |
+
finally:
|
785 |
+
# 确保日志线程停止
|
786 |
+
STOP_LOG_THREAD.set()
|
787 |
+
logging.info("应用程序关闭")
|
788 |
|
789 |
if __name__ == "__main__":
|
790 |
main()
|