White74195 commited on
Commit
b9d9271
·
verified ·
1 Parent(s): 1d6bdab

Upload 302 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .container/.dockerignore +74 -0
  2. .container/DOCKER_README.md +298 -0
  3. .container/Dockerfile +106 -0
  4. .container/build_docker.bat +147 -0
  5. .container/build_docker.sh +150 -0
  6. .container/check_docker.bat +62 -0
  7. .container/check_docker.sh +92 -0
  8. .container/docker-compose.yml +52 -0
  9. .container/run_in_docker.bat +116 -0
  10. .container/run_in_docker.sh +135 -0
  11. .gitattributes +8 -0
  12. .gitignore +60 -0
  13. DOCKER_README_en.md +298 -0
  14. README.md +311 -0
  15. README_zh.md +296 -0
  16. assets/community.png +3 -0
  17. assets/community_2.png +3 -0
  18. assets/community_3.jpg +3 -0
  19. assets/community_4.jpg +3 -0
  20. assets/community_5.jpg +3 -0
  21. assets/meetup.jpg +3 -0
  22. assets/owl_architecture.png +3 -0
  23. assets/qr_code.jpg +3 -0
  24. licenses/LICENSE +13 -0
  25. licenses/license_template.txt +13 -0
  26. licenses/update_license.py +132 -0
  27. owl/.env_template +28 -0
  28. owl/camel/__init__.py +25 -0
  29. owl/camel/agents/__init__.py +44 -0
  30. owl/camel/agents/base.py +29 -0
  31. owl/camel/agents/chat_agent.py +1411 -0
  32. owl/camel/agents/critic_agent.py +202 -0
  33. owl/camel/agents/deductive_reasoner_agent.py +303 -0
  34. owl/camel/agents/embodied_agent.py +201 -0
  35. owl/camel/agents/knowledge_graph_agent.py +259 -0
  36. owl/camel/agents/role_assignment_agent.py +141 -0
  37. owl/camel/agents/search_agent.py +133 -0
  38. owl/camel/agents/task_agent.py +410 -0
  39. owl/camel/agents/tool_agents/__init__.py +20 -0
  40. owl/camel/agents/tool_agents/base.py +39 -0
  41. owl/camel/agents/tool_agents/hugging_face_tool_agent.py +206 -0
  42. owl/camel/benchmarks/__init__.py +17 -0
  43. owl/camel/benchmarks/base.py +152 -0
  44. owl/camel/bots/__init__.py +34 -0
  45. owl/camel/bots/discord_app.py +138 -0
  46. owl/camel/bots/slack/__init__.py +30 -0
  47. owl/camel/bots/slack/models.py +158 -0
  48. owl/camel/bots/slack/slack_app.py +255 -0
  49. owl/camel/bots/telegram_bot.py +82 -0
  50. owl/camel/configs/__init__.py +76 -0
.container/.dockerignore ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitignore
4
+ .github
5
+
6
+ # Docker
7
+ Dockerfile
8
+ docker-compose.yml
9
+ .dockerignore
10
+ DOCKER_README.md
11
+ run_in_docker.sh
12
+
13
+ # Python
14
+ __pycache__/
15
+ *.py[cod]
16
+ *$py.class
17
+ *.so
18
+ .Python
19
+ env/
20
+ build/
21
+ develop-eggs/
22
+ dist/
23
+ downloads/
24
+ eggs/
25
+ .eggs/
26
+ lib/
27
+ lib64/
28
+ parts/
29
+ sdist/
30
+ var/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ .pytest_cache/
35
+ .coverage
36
+ htmlcov/
37
+
38
+ # 虚拟环境
39
+ venv/
40
+ ENV/
41
+ env/
42
+ .env
43
+
44
+ # IDE
45
+ .idea/
46
+ .vscode/
47
+ *.swp
48
+ *.swo
49
+ .DS_Store
50
+
51
+ # 临时文件
52
+ temp_*
53
+ *.tmp
54
+ *.log
55
+ *.bak
56
+
57
+ # 缓存
58
+ .cache/
59
+ .npm/
60
+ .yarn/
61
+
62
+ # 大型数据文件
63
+ *.csv
64
+ *.sqlite
65
+ *.db
66
+ *.hdf5
67
+ *.h5
68
+ *.parquet
69
+ *.feather
70
+ *.pkl
71
+ *.pickle
72
+
73
+ # 数据目录
74
+ data/
.container/DOCKER_README.md ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OWL项目Docker使用指南
2
+
3
+ 本文档提供了如何使用Docker运行OWL项目的详细说明。
4
+
5
+ ## 前提条件
6
+
7
+ - 安装 [Docker](https://docs.docker.com/get-docker/)
8
+ - 安装 [Docker Compose](https://docs.docker.com/compose/install/) (推荐v2.x版本)
9
+ - 获取必要的API密钥(OpenAI API等)
10
+
11
+ ## 技术说明
12
+
13
+ 本Docker配置使用了以下技术来确保OWL项目在容器中正常运行:
14
+
15
+ - **Xvfb**:虚拟帧缓冲区,用于在无显示器的环境中模拟X服务器
16
+ - **Playwright**:用于自动化浏览器操作,配置为无头模式
17
+ - **共享内存**:增加了共享内存大小,以提高浏览器性能
18
+ - **BuildKit**:使用Docker BuildKit加速构建过程
19
+ - **缓存优化**:使用持久化卷缓存pip和Playwright依赖
20
+ - **跨平台兼容**:提供了适用于Windows和macOS/Linux的脚本
21
+
22
+ ## Docker Compose版本说明
23
+
24
+ 本项目使用的docker-compose.yml文件兼容Docker Compose v2.x版本。如果您使用的是较旧的Docker Compose v1.x版本,可能需要手动添加版本号:
25
+
26
+ ```yaml
27
+ version: '3'
28
+
29
+ services:
30
+ # ...其余配置保持不变
31
+ ```
32
+
33
+ ## 快速开始
34
+
35
+ ### 0. 检查环境
36
+
37
+ 首先,运行检查脚本确保您的环境已准备好:
38
+
39
+ #### 在macOS/Linux上检查
40
+
41
+ ```bash
42
+ # 先给脚本添加执行权限
43
+ chmod +x check_docker.sh
44
+
45
+ # 运行检查脚本
46
+ ./check_docker.sh
47
+ ```
48
+
49
+ #### 在Windows上检查
50
+
51
+ ```cmd
52
+ check_docker.bat
53
+ ```
54
+
55
+ 如果检查脚本发现任何问题,请按照提示进行修复。
56
+
57
+ ### 1. 配置环境变量
58
+
59
+ 复制环境变量模板文件并填写必要的API密钥:
60
+
61
+ ```bash
62
+ cp owl/.env_template owl/.env
63
+ ```
64
+
65
+ 然后编辑 `owl/.env` 文件,填写必要的API密钥,例如:
66
+
67
+ ```
68
+ OPENAI_API_KEY=your_openai_api_key
69
+ GOOGLE_API_KEY=your_google_api_key
70
+ SEARCH_ENGINE_ID=your_search_engine_id
71
+ ```
72
+
73
+ ### 2. 快速构建Docker镜像
74
+
75
+ #### 在macOS/Linux上构建
76
+
77
+ 使用提供的Shell脚本,可以加速Docker镜像的构建:
78
+
79
+ ```bash
80
+ # 先给脚本添加执行权限
81
+ chmod +x build_docker.sh
82
+
83
+ # 运行构建脚本
84
+ ./build_docker.sh
85
+ ```
86
+
87
+ #### 在Windows上构建
88
+
89
+ 使用提供的批处理文件:
90
+
91
+ ```cmd
92
+ build_docker.bat
93
+ ```
94
+
95
+ 或者使用标准方式构建并启动:
96
+
97
+ ```bash
98
+ # 使用BuildKit加速构建
99
+ set DOCKER_BUILDKIT=1
100
+ set COMPOSE_DOCKER_CLI_BUILD=1
101
+ docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1
102
+
103
+ # 启动容器
104
+ docker-compose up -d
105
+ ```
106
+
107
+ ### 3. 交互式使用容器
108
+
109
+ 容器启动后,会自动进入交互式shell环境,并显示欢迎信息和可用脚本列表:
110
+
111
+ ```bash
112
+ # 进入容器(如果没有自动进入)
113
+ docker-compose exec owl bash
114
+ ```
115
+
116
+ 在容器内,您可以直接运行任何可用的脚本:
117
+
118
+ ```bash
119
+ # 运行默认脚本
120
+ xvfb-python run.py
121
+
122
+ # 运行DeepSeek示例
123
+ xvfb-python run_deepseek_example.py
124
+
125
+ # 运行脚本并传递查询参数
126
+ xvfb-python run.py "什么是人工智能?"
127
+ ```
128
+
129
+ ### 4. 使用外部脚本运行查询
130
+
131
+ #### 在macOS/Linux上运行
132
+
133
+ ```bash
134
+ # 先给脚本添加执行权限
135
+ chmod +x run_in_docker.sh
136
+
137
+ # 默认使用 run.py 脚本
138
+ ./run_in_docker.sh "你的问题"
139
+
140
+ # 指定使用特定脚本
141
+ ./run_in_docker.sh run_deepseek_example.py "你的问题"
142
+ ```
143
+
144
+ #### 在Windows上运行
145
+
146
+ ```cmd
147
+ REM 默认使用 run.py 脚本
148
+ run_in_docker.bat "你的问题"
149
+
150
+ REM 指定使用特定脚本
151
+ run_in_docker.bat run_deepseek_example.py "你的问题"
152
+ ```
153
+
154
+ **可用脚本**:
155
+ - `run.py` - 默认脚本,使用OpenAI GPT-4o模型
156
+ - `run_deepseek_example.py` - 使用DeepSeek模型
157
+ - `run_gaia_roleplaying.py` - GAIA基准测试脚本
158
+
159
+ ## 目录挂载
160
+
161
+ Docker Compose配置中已经设置了以下挂载点:
162
+
163
+ - `./owl/.env:/app/owl/.env`:挂载环境变量文件,方便修改API密钥
164
+ - `./data:/app/data`:挂载数据目录,用于存储和访问数据文件
165
+ - `playwright-cache`:持久化卷,用于缓存Playwright浏览器
166
+ - `pip-cache`:持久化卷,用于缓存pip包
167
+
168
+ ## 环境变量
169
+
170
+ 您可以通过以下两种方式设置环境变量:
171
+
172
+ 1. 修改 `owl/.env` 文件
173
+ 2. 在 `docker-compose.yml` 文件的 `environment` 部分添加环境变量
174
+
175
+ ## 构建优化
176
+
177
+ 本Docker配置包含多项构建优化:
178
+
179
+ 1. **使用国内镜像源**:使用清华大学镜像源加速pip包下载
180
+ 2. **层优化**:减少Dockerfile中的层数,提高构建效率
181
+ 3. **缓存利用**:
182
+ - 启用pip缓存,避免重复下载依赖包
183
+ - 使用Docker BuildKit内联缓存
184
+ - 合理安排Dockerfile指令顺序,最大化利用缓存
185
+ 4. **BuildKit**:启用Docker BuildKit加速构建
186
+ 5. **持久化缓存**:
187
+ - 使用Docker卷缓存pip包(`pip-cache`)
188
+ - 使用Docker卷缓存Playwright浏览器(`playwright-cache`)
189
+ - 本地缓存目录(`.docker-cache`)
190
+
191
+ ### 缓存清理
192
+
193
+ 如果需要清理缓存,可以使用以下命令:
194
+
195
+ ```bash
196
+ # 清理Docker构建缓存
197
+ docker builder prune
198
+
199
+ # 清理Docker卷(会删除所有未使用的卷,包括缓存卷)
200
+ docker volume prune
201
+
202
+ # 清理本��缓存目录
203
+ rm -rf .docker-cache
204
+ ```
205
+
206
+ ## 跨平台兼容性
207
+
208
+ 本项目提供了适用于不同操作系统的脚本:
209
+
210
+ 1. **检查脚本**:
211
+ - `check_docker.sh`(macOS/Linux):检查Docker环境
212
+ - `check_docker.bat`(Windows):检查Docker环境
213
+
214
+ 2. **构建脚本**:
215
+ - `build_docker.sh`(macOS/Linux):构建Docker镜像
216
+ - `build_docker.bat`(Windows):构建Docker镜像
217
+
218
+ 3. **运行脚本**:
219
+ - `run_in_docker.sh`(macOS/Linux):运行Docker容器中的脚本
220
+ - `run_in_docker.bat`(Windows):运行Docker容器中的脚本
221
+
222
+ 这些脚本会自动检测操作系统类型,并使用适当的命令。
223
+
224
+ ## 故障排除
225
+
226
+ ### 容器无法启动
227
+
228
+ 检查日志以获取更多信息:
229
+
230
+ ```bash
231
+ docker-compose logs
232
+ ```
233
+
234
+ ### API密钥问题
235
+
236
+ 确保您已经在 `owl/.env` 文件中正确设置了所有必要的API密钥。
237
+
238
+ ### Docker Compose警告
239
+
240
+ 如果您看到关于`version`属性过时的警告:
241
+
242
+ ```
243
+ WARN[0000] docker-compose.yml: the attribute `version` is obsolete
244
+ ```
245
+
246
+ 这是因为您使用的是Docker Compose v2.x,它不再需要显式指定版本号。我们已经从配置文件中移除了这个属性,所以您不会再看到这个警告。
247
+
248
+ ### 浏览器相关问题
249
+
250
+ 如果遇到浏览器相关的问题,可以尝试以下解决方案:
251
+
252
+ 1. 确保在Docker容器中使用`xvfb-python`命令运行Python脚本
253
+ 2. 检查是否正确安装了Xvfb和相关依赖
254
+ 3. 增加共享内存大小(在docker-compose.yml中已设置为2GB)
255
+
256
+ ### 构建速度慢
257
+
258
+ 如果构建速度慢,可以尝试以下解决方案:
259
+
260
+ 1. 确保启用了Docker BuildKit(`DOCKER_BUILDKIT=1`)
261
+ 2. 确保启用了pip缓存(已在docker-compose.yml中配置)
262
+ 3. 使用`--build-arg BUILDKIT_INLINE_CACHE=1`参数构建(已在构建脚本中配置)
263
+ 4. 如果是首次构建,下载依赖包可能需要较长时间,后续构建会更快
264
+
265
+ ### Windows特有问题
266
+
267
+ 如果在Windows上遇到问题:
268
+
269
+ 1. 确保使用管理员权限运行命令提示符或PowerShell
270
+ 2. 如果遇到路径问题,尝试使用正斜杠(/)而不是反斜杠(\)
271
+ 3. 如果遇到Docker Compose命令问题,尝试使用`docker compose`(无连字符)
272
+
273
+ ### 内存不足
274
+
275
+ 如果遇到内存不足的问题,可以在 `docker-compose.yml` 文件中调整资源限制:
276
+
277
+ ```yaml
278
+ services:
279
+ owl:
280
+ # 其他配置...
281
+ deploy:
282
+ resources:
283
+ limits:
284
+ cpus: '4' # 增加CPU核心数
285
+ memory: 8G # 增加内存限制
286
+ ```
287
+
288
+ ## 自定义Docker镜像
289
+
290
+ 如果需要自定义Docker镜像,可以修改 `Dockerfile` 文件,然后重新构建:
291
+
292
+ ```bash
293
+ # macOS/Linux
294
+ ./build_docker.sh
295
+
296
+ # Windows
297
+ build_docker.bat
298
+ ```
.container/Dockerfile ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用ARG定义可配置的构建参数
2
+ ARG PYTHON_VERSION=3.10
3
+ ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
4
+ ARG PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
5
+
6
+ # 第一阶段:构建依赖
7
+ FROM python:${PYTHON_VERSION}-slim AS builder
8
+
9
+ # 设置工作目录
10
+ WORKDIR /build
11
+
12
+ # 设置pip镜像源以加速下载
13
+ ARG PIP_INDEX_URL
14
+ RUN pip config set global.index-url ${PIP_INDEX_URL}
15
+
16
+ # 安装构建依赖
17
+ RUN apt-get update && apt-get install -y --no-install-recommends \
18
+ build-essential \
19
+ && apt-get clean \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # 复制并安装requirements.txt
23
+ COPY requirements.txt .
24
+ RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
25
+
26
+ # 第二阶段:运行时环境
27
+ FROM python:${PYTHON_VERSION}-slim
28
+
29
+ # 添加构建信息标签
30
+ ARG BUILD_DATE
31
+ ARG VERSION
32
+ LABEL org.opencontainers.image.created="${BUILD_DATE}" \
33
+ org.opencontainers.image.version="${VERSION}" \
34
+ org.opencontainers.image.title="OWL Project" \
35
+ org.opencontainers.image.description="OWL Project Docker Image" \
36
+ org.opencontainers.image.source="https://github.com/yourusername/owl"
37
+
38
+ # 设置工作目录
39
+ WORKDIR /app
40
+
41
+ # 设置pip镜像源以加速下载
42
+ ARG PIP_INDEX_URL
43
+ RUN pip config set global.index-url ${PIP_INDEX_URL}
44
+
45
+ # 从builder阶段复制已安装的Python包
46
+ COPY --from=builder /install /usr/local
47
+
48
+ # 优化apt安装,减少层数
49
+ RUN apt-get update && apt-get install -y --no-install-recommends \
50
+ curl \
51
+ git \
52
+ ffmpeg \
53
+ libsm6 \
54
+ libxext6 \
55
+ # 添加xvfb和相关依赖
56
+ xvfb \
57
+ xauth \
58
+ x11-utils \
59
+ && apt-get clean \
60
+ && rm -rf /var/lib/apt/lists/*
61
+
62
+ # 安装 Playwright 依赖(使用国内镜像源)
63
+ ENV PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright
64
+ ARG PLAYWRIGHT_DOWNLOAD_HOST
65
+ ENV PLAYWRIGHT_DOWNLOAD_HOST=${PLAYWRIGHT_DOWNLOAD_HOST}
66
+ RUN pip install --no-cache-dir playwright && \
67
+ playwright install --with-deps chromium
68
+
69
+ # 创建非root用户
70
+ RUN groupadd -r owl && useradd -r -g owl -m owl
71
+
72
+ # 复制项目文件
73
+ COPY owl/ ./owl/
74
+ COPY licenses/ ./licenses/
75
+ COPY assets/ ./assets/
76
+ COPY README.md .
77
+ COPY README_zh.md .
78
+
79
+ # 设置环境变量文件
80
+ COPY owl/.env_template ./owl/.env
81
+
82
+ # 创建启动脚本
83
+ RUN echo '#!/bin/bash\nxvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" python "$@"' > /usr/local/bin/xvfb-python && \
84
+ chmod +x /usr/local/bin/xvfb-python
85
+
86
+ # 创建欢迎脚本
87
+ RUN echo '#!/bin/bash\necho "欢迎使用OWL项目Docker环境!"\necho ""\necho "可用的脚本:"\nls -1 *.py | grep -v "__" | sed "s/^/- /"\necho ""\necho "运行示例:"\necho " xvfb-python run.py # 运行默认脚本"\necho " xvfb-python run_deepseek_example.py # 运行DeepSeek示例"\necho ""\necho "或者使用自定义查询:"\necho " xvfb-python run.py \"你的问题\""\necho ""' > /usr/local/bin/owl-welcome && \
88
+ chmod +x /usr/local/bin/owl-welcome
89
+
90
+ # 设置工作目录
91
+ WORKDIR /app/owl
92
+
93
+ # 设置适当的权限
94
+ RUN chown -R owl:owl /app
95
+ RUN mkdir -p /root/.cache && chown -R owl:owl /root/.cache
96
+
97
+ # 切换到非root用户
98
+ # 注意:如果需要访问/dev/shm,可能仍需要root用户
99
+ # USER owl
100
+
101
+ # 添加健康检查
102
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
103
+ CMD python -c "import sys; sys.exit(0 if __import__('os').path.exists('/app/owl') else 1)"
104
+
105
+ # 容器启动命令
106
+ CMD ["/bin/bash", "-c", "owl-welcome && /bin/bash"]
.container/build_docker.bat ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal enabledelayedexpansion
3
+
4
+ echo 在Windows上构建Docker镜像...
5
+
6
+ REM 设置配置变量
7
+ set CACHE_DIR=.docker-cache\pip
8
+ set BUILD_ARGS=--build-arg BUILDKIT_INLINE_CACHE=1
9
+ set COMPOSE_FILE=docker-compose.yml
10
+
11
+ REM 解析命令行参数
12
+ set CLEAN_CACHE=0
13
+ set REBUILD=0
14
+ set SERVICE=
15
+
16
+ :parse_args
17
+ if "%~1"=="" goto :end_parse_args
18
+ if /i "%~1"=="--clean" (
19
+ set CLEAN_CACHE=1
20
+ shift
21
+ goto :parse_args
22
+ )
23
+ if /i "%~1"=="--rebuild" (
24
+ set REBUILD=1
25
+ shift
26
+ goto :parse_args
27
+ )
28
+ if /i "%~1"=="--service" (
29
+ set SERVICE=%~2
30
+ shift
31
+ shift
32
+ goto :parse_args
33
+ )
34
+ if /i "%~1"=="--help" (
35
+ echo 用法: build_docker.bat [选项]
36
+ echo 选项:
37
+ echo --clean 清理缓存目录
38
+ echo --rebuild 强制重新构建镜像
39
+ echo --service 指定要构建的服务名称
40
+ echo --help 显示此帮助信息
41
+ exit /b 0
42
+ )
43
+ shift
44
+ goto :parse_args
45
+ :end_parse_args
46
+
47
+ REM 检查Docker是否安装
48
+ where docker >nul 2>nul
49
+ if %ERRORLEVEL% NEQ 0 (
50
+ echo 错误: Docker未安装
51
+ echo 请先安装Docker Desktop: https://docs.docker.com/desktop/install/windows-install/
52
+ pause
53
+ exit /b 1
54
+ )
55
+
56
+ REM 检查Docker是否运行
57
+ docker info >nul 2>nul
58
+ if %ERRORLEVEL% NEQ 0 (
59
+ echo 错误: Docker未运行
60
+ echo 请启动Docker Desktop应用程序
61
+ pause
62
+ exit /b 1
63
+ )
64
+
65
+ REM 检查docker-compose.yml文件是否存在
66
+ if not exist "%COMPOSE_FILE%" (
67
+ echo 错误: 未找到%COMPOSE_FILE%文件
68
+ echo 请确保在正确的目录中运行此脚本
69
+ pause
70
+ exit /b 1
71
+ )
72
+
73
+ REM 检查Docker Compose命令
74
+ where docker-compose >nul 2>nul
75
+ if %ERRORLEVEL% EQU 0 (
76
+ set COMPOSE_CMD=docker-compose
77
+ ) else (
78
+ echo 尝试使用新的docker compose命令...
79
+ docker compose version >nul 2>nul
80
+ if %ERRORLEVEL% EQU 0 (
81
+ set COMPOSE_CMD=docker compose
82
+ ) else (
83
+ echo 错误: 未找到Docker Compose命令
84
+ echo 请确保Docker Desktop已正确安装
85
+ pause
86
+ exit /b 1
87
+ )
88
+ )
89
+
90
+ REM 设置Docker BuildKit环境变量
91
+ set DOCKER_BUILDKIT=1
92
+ set COMPOSE_DOCKER_CLI_BUILD=1
93
+
94
+ echo 启用Docker BuildKit加速构建...
95
+
96
+ REM 清理缓存(如果指定)
97
+ if %CLEAN_CACHE% EQU 1 (
98
+ echo 清理缓存目录...
99
+ if exist "%CACHE_DIR%" rmdir /s /q "%CACHE_DIR%"
100
+ )
101
+
102
+ REM 创建缓存目录
103
+ if not exist "%CACHE_DIR%" (
104
+ echo 创建缓存目录...
105
+ mkdir "%CACHE_DIR%"
106
+ )
107
+
108
+ REM 添加构建时间标记
109
+ for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
110
+ set "YEAR=%dt:~0,4%"
111
+ set "MONTH=%dt:~4,2%"
112
+ set "DAY=%dt:~6,2%"
113
+ set "HOUR=%dt:~8,2%"
114
+ set "MINUTE=%dt:~10,2%"
115
+ set "BUILD_TIME=%YEAR%%MONTH%%DAY%_%HOUR%%MINUTE%"
116
+ set "BUILD_ARGS=%BUILD_ARGS% --build-arg BUILD_TIME=%BUILD_TIME%"
117
+
118
+ REM 构建Docker镜像
119
+ echo 开始构建Docker镜像...
120
+
121
+ if "%SERVICE%"=="" (
122
+ if %REBUILD% EQU 1 (
123
+ echo 强制重新构建所有服务...
124
+ %COMPOSE_CMD% build --no-cache %BUILD_ARGS%
125
+ ) else (
126
+ %COMPOSE_CMD% build %BUILD_ARGS%
127
+ )
128
+ ) else (
129
+ if %REBUILD% EQU 1 (
130
+ echo 强制重新构建服务 %SERVICE%...
131
+ %COMPOSE_CMD% build --no-cache %BUILD_ARGS% %SERVICE%
132
+ ) else (
133
+ echo 构建服务 %SERVICE%...
134
+ %COMPOSE_CMD% build %BUILD_ARGS% %SERVICE%
135
+ )
136
+ )
137
+
138
+ if %ERRORLEVEL% EQU 0 (
139
+ echo Docker镜像构建成功!
140
+ echo 构建时间: %BUILD_TIME%
141
+ echo 可以使用以下命令启动容器:
142
+ echo %COMPOSE_CMD% up -d
143
+ ) else (
144
+ echo Docker镜像构建失败,请检查错误信息。
145
+ )
146
+
147
+ pause
.container/build_docker.sh ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # 设置配置变量
4
+ CACHE_DIR=".docker-cache/pip"
5
+ BUILD_ARGS="--build-arg BUILDKIT_INLINE_CACHE=1"
6
+ COMPOSE_FILE="docker-compose.yml"
7
+ CLEAN_CACHE=0
8
+ REBUILD=0
9
+ SERVICE=""
10
+
11
+ # 解析命令行参数
12
+ while [[ $# -gt 0 ]]; do
13
+ case "$1" in
14
+ --clean)
15
+ CLEAN_CACHE=1
16
+ shift
17
+ ;;
18
+ --rebuild)
19
+ REBUILD=1
20
+ shift
21
+ ;;
22
+ --service)
23
+ SERVICE="$2"
24
+ shift 2
25
+ ;;
26
+ --help)
27
+ echo "用法: ./build_docker.sh [选项]"
28
+ echo "选项:"
29
+ echo " --clean 清理缓存目录"
30
+ echo " --rebuild 强制重新构建镜像"
31
+ echo " --service 指定要构建的服务名称"
32
+ echo " --help 显示此帮助信息"
33
+ exit 0
34
+ ;;
35
+ *)
36
+ echo "未知选项: $1"
37
+ echo "使用 --help 查看帮助"
38
+ exit 1
39
+ ;;
40
+ esac
41
+ done
42
+
43
+ # 检测操作系统类型
44
+ OS_TYPE=$(uname -s)
45
+ echo "检测到操作系统: $OS_TYPE"
46
+
47
+ # 检查Docker是否安装
48
+ if ! command -v docker &> /dev/null; then
49
+ echo "错误: Docker未安装"
50
+ echo "请先安装Docker: https://docs.docker.com/get-docker/"
51
+ exit 1
52
+ fi
53
+
54
+ # 检查Docker是否运行
55
+ if ! docker info &> /dev/null; then
56
+ echo "错误: Docker未运行"
57
+ echo "请启动Docker服务"
58
+ exit 1
59
+ fi
60
+
61
+ # 检查docker-compose.yml文件是否存在
62
+ if [ ! -f "$COMPOSE_FILE" ]; then
63
+ echo "错误: 未找到$COMPOSE_FILE文件"
64
+ echo "请确保在正确的目录中运行此脚本"
65
+ exit 1
66
+ fi
67
+
68
+ # 设置Docker BuildKit环境变量
69
+ export DOCKER_BUILDKIT=1
70
+ export COMPOSE_DOCKER_CLI_BUILD=1
71
+
72
+ echo "启用Docker BuildKit加速构建..."
73
+
74
+ # 清理缓存(如果指定)
75
+ if [ $CLEAN_CACHE -eq 1 ]; then
76
+ echo "清理缓存目录..."
77
+ rm -rf "$CACHE_DIR"
78
+ fi
79
+
80
+ # 创建缓存目录
81
+ mkdir -p "$CACHE_DIR"
82
+
83
+ # 添加构建时间标记
84
+ BUILD_TIME=$(date +"%Y%m%d_%H%M%S")
85
+ BUILD_ARGS="$BUILD_ARGS --build-arg BUILD_TIME=$BUILD_TIME"
86
+
87
+ # 获取脚本所在目录
88
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
89
+ # 获取项目根目录(脚本所在目录的父目录)
90
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
91
+
92
+ echo "脚本目录: $SCRIPT_DIR"
93
+ echo "项目根目录: $PROJECT_ROOT"
94
+
95
+ # 切换到项目根目录
96
+ cd "$PROJECT_ROOT"
97
+
98
+ # 检查Docker Compose命令
99
+ if command -v docker-compose &> /dev/null; then
100
+ COMPOSE_CMD="docker-compose"
101
+ echo "使用 docker-compose 命令"
102
+ elif docker compose version &> /dev/null; then
103
+ COMPOSE_CMD="docker compose"
104
+ echo "使用 docker compose 命令"
105
+ else
106
+ echo "错误: 未找到Docker Compose命令"
107
+ echo "请安装Docker Compose: https://docs.docker.com/compose/install/"
108
+ exit 1
109
+ fi
110
+
111
+ # 检测CPU核心数,用于并行构建
112
+ CPU_CORES=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)
113
+ if [ $CPU_CORES -gt 2 ]; then
114
+ PARALLEL_FLAG="--parallel"
115
+ echo "检测到${CPU_CORES}个CPU核心,启用并行构建..."
116
+ else
117
+ PARALLEL_FLAG=""
118
+ fi
119
+
120
+ # 构建命令基础部分
121
+ BUILD_CMD="$COMPOSE_CMD -f \"$SCRIPT_DIR/docker-compose.yml\" build $PARALLEL_FLAG --build-arg BUILDKIT_INLINE_CACHE=1"
122
+
123
+ # 根据操作系统类型执行不同的命令
124
+ if [[ "$OS_TYPE" == "Darwin" ]]; then
125
+ # macOS
126
+ echo "在macOS上构建Docker镜像..."
127
+ eval $BUILD_CMD
128
+ elif [[ "$OS_TYPE" == "Linux" ]]; then
129
+ # Linux
130
+ echo "在Linux上构建Docker镜像..."
131
+ eval $BUILD_CMD
132
+ elif [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
133
+ # Windows
134
+ echo "在Windows上构建Docker镜像..."
135
+ eval $BUILD_CMD
136
+ else
137
+ echo "未知操作系统,尝试使用标准命令构建..."
138
+ eval $BUILD_CMD
139
+ fi
140
+
141
+ # 检查构建结果
142
+ if [ $? -eq 0 ]; then
143
+ echo "Docker镜像构建成功!"
144
+ echo "构建时间: $BUILD_TIME"
145
+ echo "可以使用以下命令启动容器:"
146
+ echo "$COMPOSE_CMD -f \"$SCRIPT_DIR/docker-compose.yml\" up -d"
147
+ else
148
+ echo "Docker镜像构建失败,请检查错误信息。"
149
+ exit 1
150
+ fi
.container/check_docker.bat ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo 检查Docker环境...
3
+
4
+ REM 检查Docker是否安装
5
+ where docker >nul 2>nul
6
+ if %ERRORLEVEL% NEQ 0 (
7
+ echo 错误: Docker未安装
8
+ echo 在Windows上安装Docker的方法:
9
+ echo 1. 访问 https://docs.docker.com/desktop/install/windows-install/ 下载Docker Desktop
10
+ echo 2. 安装并启动Docker Desktop
11
+ pause
12
+ exit /b 1
13
+ )
14
+
15
+ echo Docker已安装
16
+
17
+ REM 检查Docker Compose是否安装
18
+ where docker-compose >nul 2>nul
19
+ if %ERRORLEVEL% NEQ 0 (
20
+ echo 警告: Docker-Compose未找到,尝试使用新的docker compose命令
21
+ docker compose version >nul 2>nul
22
+ if %ERRORLEVEL% NEQ 0 (
23
+ echo 错误: Docker Compose未安装
24
+ echo Docker Desktop for Windows应该已包含Docker Compose
25
+ echo 请确保Docker Desktop已正确安装
26
+ pause
27
+ exit /b 1
28
+ ) else (
29
+ echo 使用新的docker compose命令
30
+ set COMPOSE_CMD=docker compose
31
+ )
32
+ ) else (
33
+ echo Docker-Compose已安装
34
+ set COMPOSE_CMD=docker-compose
35
+ )
36
+
37
+ REM 检查Docker是否正在运行
38
+ docker info >nul 2>nul
39
+ if %ERRORLEVEL% NEQ 0 (
40
+ echo 错误: Docker未运行
41
+ echo 请启动Docker Desktop应用程序
42
+ pause
43
+ exit /b 1
44
+ )
45
+
46
+ echo Docker正在运行
47
+
48
+ REM 检查是否有.env文件
49
+ if not exist "owl\.env" (
50
+ echo 警告: 未找到owl\.env文件
51
+ echo 请运行以下命令创建环境变量文件:
52
+ echo copy owl\.env_template owl\.env
53
+ echo 然后编辑owl\.env文件,填写必要的API密钥
54
+ ) else (
55
+ echo 环境变量文件已存在
56
+ )
57
+
58
+ echo 所有检查完成,您的系统已准备好构建和运行OWL项目的Docker容器
59
+ echo 请运行以下命令构建Docker镜像:
60
+ echo %COMPOSE_CMD% build
61
+
62
+ pause
.container/check_docker.sh ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # 检测操作系统类型
4
+ OS_TYPE=$(uname -s)
5
+ echo "检测到操作系统: $OS_TYPE"
6
+
7
+ # 检查Docker是否安装
8
+ if ! command -v docker &> /dev/null; then
9
+ echo "错误: Docker未安装"
10
+
11
+ if [[ "$OS_TYPE" == "Darwin" ]]; then
12
+ echo "在macOS上安装Docker的方法:"
13
+ echo "1. 访问 https://docs.docker.com/desktop/install/mac-install/ 下载Docker Desktop"
14
+ echo "2. 安装并启动Docker Desktop"
15
+ elif [[ "$OS_TYPE" == "Linux" ]]; then
16
+ echo "在Linux上安装Docker的方法:"
17
+ echo "1. 运行以下命令:"
18
+ echo " sudo apt-get update"
19
+ echo " sudo apt-get install docker.io docker-compose"
20
+ echo "2. 启动Docker服务:"
21
+ echo " sudo systemctl start docker"
22
+ echo " sudo systemctl enable docker"
23
+ elif [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
24
+ echo "在Windows上安装Docker的方法:"
25
+ echo "1. 访问 https://docs.docker.com/desktop/install/windows-install/ 下载Docker Desktop"
26
+ echo "2. 安装并启动Docker Desktop"
27
+ fi
28
+
29
+ exit 1
30
+ fi
31
+
32
+ echo "Docker已安装"
33
+
34
+ # 检查Docker Compose是否安装
35
+ if ! command -v docker-compose &> /dev/null; then
36
+ echo "错误: Docker Compose未安装"
37
+
38
+ if [[ "$OS_TYPE" == "Darwin" ]]; then
39
+ echo "Docker Desktop for Mac已包含Docker Compose"
40
+ elif [[ "$OS_TYPE" == "Linux" ]]; then
41
+ echo "在Linux上安装Docker Compose的方法:"
42
+ echo "1. 运行以下命令:"
43
+ echo " sudo apt-get install docker-compose"
44
+ elif [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
45
+ echo "Docker Desktop for Windows已包含Docker Compose"
46
+ fi
47
+
48
+ exit 1
49
+ fi
50
+
51
+ echo "Docker Compose已安装"
52
+
53
+ # 检查Docker是否正在运行
54
+ if ! docker info &> /dev/null; then
55
+ echo "错误: Docker未运行"
56
+
57
+ if [[ "$OS_TYPE" == "Darwin" ]]; then
58
+ echo "请启动Docker Desktop应用程序"
59
+ elif [[ "$OS_TYPE" == "Linux" ]]; then
60
+ echo "请运行以下命令启动Docker服务:"
61
+ echo "sudo systemctl start docker"
62
+ elif [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
63
+ echo "请启动Docker Desktop应用程序"
64
+ fi
65
+
66
+ exit 1
67
+ fi
68
+
69
+ echo "Docker正在运行"
70
+
71
+ # 检查是否有足够的磁盘空间
72
+ FREE_SPACE=$(df -h . | awk 'NR==2 {print $4}')
73
+ echo "可用磁盘空间: $FREE_SPACE"
74
+
75
+ # 检查是否有.env文件
76
+ if [ ! -f "owl/.env" ]; then
77
+ echo "警告: 未找到owl/.env文件"
78
+ echo "请运行以下命令创建环境变量文件:"
79
+ echo "cp owl/.env_template owl/.env"
80
+ echo "然后编辑owl/.env文件,填写必要的API密钥"
81
+ else
82
+ echo "环境变量文件已存在"
83
+ fi
84
+
85
+ echo "所有检查完成,您的系统已准备好构建和运行OWL项目的Docker容器"
86
+ echo "请运行以下命令构建Docker镜像:"
87
+
88
+ if [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
89
+ echo "build_docker.bat"
90
+ else
91
+ echo "./build_docker.sh"
92
+ fi
.container/docker-compose.yml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ owl:
3
+ build:
4
+ context: ..
5
+ dockerfile: .container/Dockerfile
6
+ args:
7
+ # 构建参数
8
+ BUILDKIT_INLINE_CACHE: 1
9
+ # 使用BuildKit加速构建
10
+ cache_from:
11
+ - python:3.10-slim
12
+ volumes:
13
+ # 挂载.env文件,方便配置API密钥
14
+ - ./owl/.env:/app/owl/.env
15
+ # 可选:挂载数据目录
16
+ - ./data:/app/data
17
+ # 挂载缓存目录,避免重复下载
18
+ - playwright-cache:/root/.cache/ms-playwright
19
+ - pip-cache:/root/.pip/cache
20
+ environment:
21
+ # 可以在这里设置环境变量,覆盖.env文件中的设置
22
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
23
+ # 添加显示相关的环境变量
24
+ - DISPLAY=:99
25
+ - PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright
26
+ # 设置Python不生成.pyc文件,减少磁盘IO
27
+ - PYTHONDONTWRITEBYTECODE=1
28
+ # 设置Python不缓冲输出,方便查看日志
29
+ - PYTHONUNBUFFERED=1
30
+ # 设置终端颜色
31
+ - TERM=xterm-256color
32
+ # 启用pip缓存
33
+ - PIP_CACHE_DIR=/root/.pip/cache
34
+ ports:
35
+ # 如果项目有Web界面,可以映射端口
36
+ - "8000:8000"
37
+ # 使用交互模式运行容器
38
+ stdin_open: true
39
+ tty: true
40
+ # 添加共享内存大小,提高浏览器性能
41
+ shm_size: 2gb
42
+ # 设置资源限制
43
+ deploy:
44
+ resources:
45
+ limits:
46
+ cpus: '2'
47
+ memory: 4G
48
+
49
+ # 定义持久化卷,用于缓存
50
+ volumes:
51
+ playwright-cache:
52
+ pip-cache:
.container/run_in_docker.bat ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal enabledelayedexpansion
3
+
4
+ REM 定义配置变量
5
+ set SERVICE_NAME=owl
6
+ set PYTHON_CMD=xvfb-python
7
+ set MAX_WAIT_SECONDS=60
8
+ set CHECK_INTERVAL_SECONDS=2
9
+
10
+ REM 检查参数
11
+ if "%~1"=="" (
12
+ echo 用法: run_in_docker.bat [脚本名称] "你的问题"
13
+ echo 例如: run_in_docker.bat run.py "什么是人工智能?"
14
+ echo 或者: run_in_docker.bat run_deepseek_example.py "什么是人工智能?"
15
+ echo 如果不指定脚本名称,默认使用 run.py
16
+ exit /b 1
17
+ )
18
+
19
+ REM 判断第一个参数是否是脚本名称
20
+ set SCRIPT_NAME=%~1
21
+ set QUERY=%~2
22
+
23
+ if "!SCRIPT_NAME:~-3!"==".py" (
24
+ REM 如果提供了第二个参数,则为查询内容
25
+ if "!QUERY!"=="" (
26
+ echo 请提供查询参数,例如: run_in_docker.bat !SCRIPT_NAME! "你的问题"
27
+ exit /b 1
28
+ )
29
+ ) else (
30
+ REM 如果第一个参数不是脚本名称,则默认使用 run.py
31
+ set QUERY=!SCRIPT_NAME!
32
+ set SCRIPT_NAME=run.py
33
+ )
34
+
35
+ REM 检查脚本是否存在
36
+ if not exist "owl\!SCRIPT_NAME!" (
37
+ echo 错误: 脚本 'owl\!SCRIPT_NAME!' 不存在
38
+ echo 可用的脚本有:
39
+ dir /b owl\*.py | findstr /v "__"
40
+ exit /b 1
41
+ )
42
+
43
+ echo 使用脚本: !SCRIPT_NAME!
44
+ echo 查询内容: !QUERY!
45
+
46
+ REM 从docker-compose.yml获取服务名称(如果文件存在)
47
+ if exist ".container\docker-compose.yml" (
48
+ for /f "tokens=*" %%a in ('findstr /r "^ [a-zA-Z0-9_-]*:" .container\docker-compose.yml') do (
49
+ set line=%%a
50
+ set service=!line:~2,-1!
51
+ if not "!service!"=="" (
52
+ REM 使用第一个找到的服务名称
53
+ set SERVICE_NAME=!service!
54
+ echo 从docker-compose.yml检测到服务名称: !SERVICE_NAME!
55
+ goto :found_service
56
+ )
57
+ )
58
+ )
59
+ :found_service
60
+
61
+ REM 确保Docker容器正在运行
62
+ docker-compose ps | findstr "!SERVICE_NAME!.*Up" > nul
63
+ if errorlevel 1 (
64
+ echo 启动Docker容器...
65
+ docker-compose up -d
66
+
67
+ REM 使用循环检查容器是否就绪
68
+ echo 等待容器启动...
69
+ set /a total_wait=0
70
+
71
+ :wait_loop
72
+ timeout /t !CHECK_INTERVAL_SECONDS! /nobreak > nul
73
+ set /a total_wait+=!CHECK_INTERVAL_SECONDS!
74
+
75
+ docker-compose ps | findstr "!SERVICE_NAME!.*Up" > nul
76
+ if errorlevel 1 (
77
+ if !total_wait! LSS !MAX_WAIT_SECONDS! (
78
+ echo 容器尚未就绪,已等待!total_wait!秒,继续等待...
79
+ goto :wait_loop
80
+ ) else (
81
+ echo 错误:容器启动超时,已等待!MAX_WAIT_SECONDS!秒
82
+ echo 请检查Docker容器状态:docker-compose ps
83
+ exit /b 1
84
+ )
85
+ ) else (
86
+ echo 容器已就绪,共等待了!total_wait!秒
87
+ )
88
+ )
89
+
90
+ REM 检查容器中是否存在xvfb-python命令
91
+ echo 检查容器中的命令...
92
+ docker-compose exec -T !SERVICE_NAME! which !PYTHON_CMD! > nul 2>&1
93
+ if errorlevel 1 (
94
+ echo 警告:容器中未找到!PYTHON_CMD!命令,尝试使用python替代
95
+ set PYTHON_CMD=python
96
+
97
+ REM 检查python命令是否存在
98
+ docker-compose exec -T !SERVICE_NAME! which python > nul 2>&1
99
+ if errorlevel 1 (
100
+ echo 错误:容器中未找到python命令
101
+ echo 请检查容器配置
102
+ exit /b 1
103
+ )
104
+ )
105
+
106
+ REM 在容器中运行指定的脚本,传递查询参数
107
+ echo 在Docker容器中使用!PYTHON_CMD!运行脚本...
108
+ docker-compose exec -T !SERVICE_NAME! !PYTHON_CMD! !SCRIPT_NAME! "!QUERY!"
109
+
110
+ if errorlevel 0 (
111
+ echo 查询完成!
112
+ ) else (
113
+ echo 查询执行失败,请检查错误信息。
114
+ )
115
+
116
+ pause
.container/run_in_docker.sh ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # 定义配置变量
4
+ SERVICE_NAME="owl"
5
+ PYTHON_CMD="xvfb-python"
6
+ MAX_WAIT_SECONDS=60
7
+ CHECK_INTERVAL_SECONDS=2
8
+
9
+ # 检测操作系统类型
10
+ OS_TYPE=$(uname -s)
11
+ echo "检测到操作系统: $OS_TYPE"
12
+
13
+ # 检查是否提供了查询参数
14
+ if [ $# -lt 1 ]; then
15
+ echo "用法: ./run_in_docker.sh [脚本名称] '你的问题'"
16
+ echo "例如: ./run_in_docker.sh run.py '什么是人工智能?'"
17
+ echo "或者: ./run_in_docker.sh run_deepseek_example.py '什么是人工智能?'"
18
+ echo "如果不指定脚本名称,默认使用 run.py"
19
+ exit 1
20
+ fi
21
+
22
+ # 判断第一个参数是否是脚本名称
23
+ if [[ $1 == *.py ]]; then
24
+ SCRIPT_NAME="$1"
25
+ # 如果提供了第二个参数,则为查询内容
26
+ if [ $# -ge 2 ]; then
27
+ QUERY="$2"
28
+ else
29
+ echo "请提供查询参数,例如: ./run_in_docker.sh $SCRIPT_NAME '你的问题'"
30
+ exit 1
31
+ fi
32
+ else
33
+ # 如果第一个参数不是脚本名称,则默认使用 run.py
34
+ SCRIPT_NAME="run.py"
35
+ QUERY="$1"
36
+ fi
37
+
38
+ # 检查脚本是否存在
39
+ if [ ! -f "owl/$SCRIPT_NAME" ]; then
40
+ echo "错误: 脚本 'owl/$SCRIPT_NAME' 不存在"
41
+ echo "可用的脚本有:"
42
+ if [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
43
+ find owl -name "*.py" | grep -v "__" | sed 's/\\/\//g'
44
+ else
45
+ ls -1 owl/*.py | grep -v "__"
46
+ fi
47
+ exit 1
48
+ fi
49
+
50
+ echo "使用脚本: $SCRIPT_NAME"
51
+ echo "查询内容: $QUERY"
52
+
53
+ # 从docker-compose.yml获取服务名称(如果文件存在)
54
+ if [ -f ".container/docker-compose.yml" ]; then
55
+ DETECTED_SERVICE=$(grep -E "^ [a-zA-Z0-9_-]*:" .container/docker-compose.yml | head -1 | sed 's/^ \(.*\):.*/\1/')
56
+ if [ ! -z "$DETECTED_SERVICE" ]; then
57
+ SERVICE_NAME="$DETECTED_SERVICE"
58
+ echo "从docker-compose.yml检测到服务名称: $SERVICE_NAME"
59
+ fi
60
+ fi
61
+
62
+ # 检查Docker Compose命令
63
+ if command -v docker-compose &> /dev/null; then
64
+ COMPOSE_CMD="docker-compose"
65
+ elif docker compose version &> /dev/null; then
66
+ COMPOSE_CMD="docker compose"
67
+ else
68
+ echo "错误: 未找到Docker Compose命令"
69
+ exit 1
70
+ fi
71
+
72
+ # 确保Docker容器正在运行
73
+ CONTAINER_RUNNING=$($COMPOSE_CMD ps | grep -c "$SERVICE_NAME.*Up" || true)
74
+ if [ "$CONTAINER_RUNNING" -eq 0 ]; then
75
+ echo "启动Docker容器..."
76
+ $COMPOSE_CMD up -d
77
+
78
+ # 使用循环检查容器是否就绪
79
+ echo "等待容器启动..."
80
+ TOTAL_WAIT=0
81
+
82
+ while [ $TOTAL_WAIT -lt $MAX_WAIT_SECONDS ]; do
83
+ sleep $CHECK_INTERVAL_SECONDS
84
+ TOTAL_WAIT=$((TOTAL_WAIT + CHECK_INTERVAL_SECONDS))
85
+
86
+ CONTAINER_RUNNING=$($COMPOSE_CMD ps | grep -c "$SERVICE_NAME.*Up" || true)
87
+ if [ "$CONTAINER_RUNNING" -gt 0 ]; then
88
+ echo "容器已就绪,共等待了 $TOTAL_WAIT 秒"
89
+ break
90
+ else
91
+ echo "容器尚未就绪,已等待 $TOTAL_WAIT 秒,继续等待..."
92
+ fi
93
+ done
94
+
95
+ if [ "$CONTAINER_RUNNING" -eq 0 ]; then
96
+ echo "错误:容器启动超时,已等待 $MAX_WAIT_SECONDS 秒"
97
+ echo "请检查Docker容器状态:$COMPOSE_CMD ps"
98
+ exit 1
99
+ fi
100
+ fi
101
+
102
+ # 检查容器中是否存在指定的Python命令
103
+ echo "检查容器中的命令..."
104
+ if ! $COMPOSE_CMD exec -T $SERVICE_NAME which $PYTHON_CMD &> /dev/null; then
105
+ echo "警告:容器中未找到 $PYTHON_CMD 命令,尝试使用python替代"
106
+ PYTHON_CMD="python"
107
+
108
+ # 检查python命令是否存在
109
+ if ! $COMPOSE_CMD exec -T $SERVICE_NAME which python &> /dev/null; then
110
+ echo "错误:容器中未找到python命令"
111
+ echo "请检查容器配置"
112
+ exit 1
113
+ fi
114
+ fi
115
+
116
+ # 在容器中运行指定的脚本,传递查询参数
117
+ echo "在Docker容器中使用 $PYTHON_CMD 运行脚本..."
118
+
119
+ # 根据操作系统类型执行不同的命令
120
+ if [[ "$OS_TYPE" == MINGW* ]] || [[ "$OS_TYPE" == CYGWIN* ]] || [[ "$OS_TYPE" == MSYS* ]]; then
121
+ # Windows可能需要特殊处理引号
122
+ winpty $COMPOSE_CMD exec -T $SERVICE_NAME $PYTHON_CMD $SCRIPT_NAME "$QUERY"
123
+ RESULT=$?
124
+ else
125
+ # macOS 或 Linux
126
+ $COMPOSE_CMD exec -T $SERVICE_NAME $PYTHON_CMD $SCRIPT_NAME "$QUERY"
127
+ RESULT=$?
128
+ fi
129
+
130
+ # 检查命令执行结果
131
+ if [ $RESULT -eq 0 ]; then
132
+ echo "查询完成!"
133
+ else
134
+ echo "查询执行失败,请检查错误信息。"
135
+ fi
.gitattributes CHANGED
@@ -6,3 +6,11 @@ owl-main/assets/community.png filter=lfs diff=lfs merge=lfs -text
6
  owl-main/assets/meetup.jpg filter=lfs diff=lfs merge=lfs -text
7
  owl-main/assets/owl_architecture.png filter=lfs diff=lfs merge=lfs -text
8
  owl-main/assets/qr_code.jpg filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
6
  owl-main/assets/meetup.jpg filter=lfs diff=lfs merge=lfs -text
7
  owl-main/assets/owl_architecture.png filter=lfs diff=lfs merge=lfs -text
8
  owl-main/assets/qr_code.jpg filter=lfs diff=lfs merge=lfs -text
9
+ assets/community_2.png filter=lfs diff=lfs merge=lfs -text
10
+ assets/community_3.jpg filter=lfs diff=lfs merge=lfs -text
11
+ assets/community_4.jpg filter=lfs diff=lfs merge=lfs -text
12
+ assets/community_5.jpg filter=lfs diff=lfs merge=lfs -text
13
+ assets/community.png filter=lfs diff=lfs merge=lfs -text
14
+ assets/meetup.jpg filter=lfs diff=lfs merge=lfs -text
15
+ assets/owl_architecture.png filter=lfs diff=lfs merge=lfs -text
16
+ assets/qr_code.jpg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ **/__pycache__/
4
+ */__pycache__/*
5
+ *.py[cod]
6
+ *$py.class
7
+ *.so
8
+ .Python
9
+ build/
10
+ develop-eggs/
11
+ .dist
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+
25
+ # Virtual Environment
26
+ venv/
27
+ env/
28
+ ENV/
29
+ .env
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+ .DS_Store
37
+
38
+ # Project specific
39
+ owl/data
40
+ owl/tmp
41
+ owl/.env
42
+ owl/utils/__pycache__/
43
+
44
+ # Logs
45
+ *.log
46
+ logs/
47
+ log/
48
+
49
+ # Coverage reports
50
+ htmlcov/
51
+ .tox/
52
+ .coverage
53
+ .coverage.*
54
+ .cache
55
+ coverage.xml
56
+ *.cover
57
+
58
+ owl/camel/types/__pycache__/
59
+ owl/camel/__pycache__/
60
+ owl/camel/utils/__pycache_/
DOCKER_README_en.md ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OWL Project Docker Usage Guide
2
+
3
+ This document provides detailed instructions on how to run the OWL project using Docker.
4
+
5
+ ## Prerequisites
6
+
7
+ • Install [Docker](https://docs.docker.com/get-docker/)
8
+ • Install [Docker Compose](https://docs.docker.com/compose/install/) (recommended v2.x version)
9
+ • Obtain necessary API keys (OpenAI API, etc.)
10
+
11
+ ## Technical Notes
12
+
13
+ This Docker configuration uses the following technologies to ensure the OWL project runs smoothly in containers:
14
+
15
+ • **Xvfb**: Virtual framebuffer, used to simulate an X server in a headless environment
16
+ • **Playwright**: Used for browser automation, configured in headless mode
17
+ • **Shared Memory**: Increased shared memory size to improve browser performance
18
+ • **BuildKit**: Uses Docker BuildKit to accelerate the build process
19
+ • **Cache Optimization**: Uses persistent volumes to cache pip and Playwright dependencies
20
+ • **Cross-Platform Compatibility**: Provides scripts for both Windows and macOS/Linux
21
+
22
+ ## Docker Compose Version Notes
23
+
24
+ The docker-compose.yml file used in this project is compatible with Docker Compose v2.x. If you are using an older Docker Compose v1.x version, you may need to manually add the version number:
25
+
26
+ ```yaml
27
+ version: '3'
28
+
29
+ services:
30
+ # ...rest of the configuration remains unchanged
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### 0. Check Environment
36
+
37
+ First, run the check script to ensure your environment is ready:
38
+
39
+ #### Check on macOS/Linux
40
+
41
+ ```bash
42
+ # First, add execute permissions to the script
43
+ chmod +x check_docker.sh
44
+
45
+ # Run the check script
46
+ ./check_docker.sh
47
+ ```
48
+
49
+ #### Check on Windows
50
+
51
+ ```cmd
52
+ check_docker.bat
53
+ ```
54
+
55
+ If the check script finds any issues, please follow the prompts to fix them.
56
+
57
+ ### 1. Configure Environment Variables
58
+
59
+ Copy the environment variable template file and fill in the necessary API keys:
60
+
61
+ ```bash
62
+ cp owl/.env_template owl/.env
63
+ ```
64
+
65
+ Then edit the `owl/.env` file and fill in the necessary API keys, for example:
66
+
67
+ ```
68
+ OPENAI_API_KEY=your_openai_api_key
69
+ GOOGLE_API_KEY=your_google_api_key
70
+ SEARCH_ENGINE_ID=your_search_engine_id
71
+ ```
72
+
73
+ ### 2. Quick Build Docker Image
74
+
75
+ #### Build on macOS/Linux
76
+
77
+ Use the provided shell script to speed up the Docker image build:
78
+
79
+ ```bash
80
+ # First, add execute permissions to the script
81
+ chmod +x build_docker.sh
82
+
83
+ # Run the build script
84
+ ./build_docker.sh
85
+ ```
86
+
87
+ #### Build on Windows
88
+
89
+ Use the provided batch file:
90
+
91
+ ```cmd
92
+ build_docker.bat
93
+ ```
94
+
95
+ Or build and start using the standard method:
96
+
97
+ ```bash
98
+ # Use BuildKit to accelerate the build
99
+ set DOCKER_BUILDKIT=1
100
+ set COMPOSE_DOCKER_CLI_BUILD=1
101
+ docker-compose build --build-arg BUILDKIT_INLINE_CACHE=1
102
+
103
+ # Start the container
104
+ docker-compose up -d
105
+ ```
106
+
107
+ ### 3. Interactive Use of the Container
108
+
109
+ After the container starts, it will automatically enter an interactive shell environment and display a welcome message and a list of available scripts:
110
+
111
+ ```bash
112
+ # Enter the container (if not automatically entered)
113
+ docker-compose exec owl bash
114
+ ```
115
+
116
+ Inside the container, you can directly run any available script:
117
+
118
+ ```bash
119
+ # Run the default script
120
+ xvfb-python run.py
121
+
122
+ # Run the DeepSeek example
123
+ xvfb-python run_deepseek_example.py
124
+
125
+ # Run the script and pass query parameters
126
+ xvfb-python run.py "What is artificial intelligence?"
127
+ ```
128
+
129
+ ### 4. Run Queries Using External Scripts
130
+
131
+ #### Run on macOS/Linux
132
+
133
+ ```bash
134
+ # First, add execute permissions to the script
135
+ chmod +x run_in_docker.sh
136
+
137
+ # Default to using the run.py script
138
+ ./run_in_docker.sh "your question"
139
+
140
+ # Specify a particular script
141
+ ./run_in_docker.sh run_deepseek_example.py "your question"
142
+ ```
143
+
144
+ #### Run on Windows
145
+
146
+ ```cmd
147
+ REM Default to using the run.py script
148
+ run_in_docker.bat "your question"
149
+
150
+ REM Specify a particular script
151
+ run_in_docker.bat run_deepseek_example.py "your question"
152
+ ```
153
+
154
+ **Available Scripts**:
155
+ • `run.py` - Default script, uses OpenAI GPT-4o model
156
+ • `run_deepseek_example.py` - Uses the DeepSeek model
157
+ • `run_gaia_roleplaying.py` - GAIA benchmark script
158
+
159
+ ## Directory Mounts
160
+
161
+ The Docker Compose configuration has set up the following mount points:
162
+
163
+ • `./owl/.env:/app/owl/.env`: Mounts the environment variable file for easy modification of API keys
164
+ • `./data:/app/data`: Mounts the data directory for storing and accessing data files
165
+ • `playwright-cache`: Persistent volume for caching Playwright browsers
166
+ • `pip-cache`: Persistent volume for caching pip packages
167
+
168
+ ## Environment Variables
169
+
170
+ You can set environment variables in two ways:
171
+
172
+ 1. Modify the `owl/.env` file
173
+ 2. Add environment variables in the `environment` section of the `docker-compose.yml` file
174
+
175
+ ## Build Optimization
176
+
177
+ This Docker configuration includes several build optimizations:
178
+
179
+ 1. **Use of Domestic Mirror Sources**: Uses Tsinghua University mirror sources to accelerate pip package downloads
180
+ 2. **Layer Optimization**: Reduces the number of layers in the Dockerfile to improve build efficiency
181
+ 3. **Cache Utilization**:
182
+ • Enables pip caching to avoid repeated dependency downloads
183
+ • Uses Docker BuildKit inline caching
184
+ • Arranges Dockerfile instructions to maximize cache utilization
185
+ 4. **BuildKit**: Enables Docker BuildKit to accelerate builds
186
+ 5. **Persistent Caching**:
187
+ • Uses Docker volumes to cache pip packages (`pip-cache`)
188
+ • Uses Docker volumes to cache Playwright browsers (`playwright-cache`)
189
+ • Local cache directory (`.docker-cache`)
190
+
191
+ ### Cache Cleanup
192
+
193
+ If you need to clean the cache, you can use the following commands:
194
+
195
+ ```bash
196
+ # Clean Docker build cache
197
+ docker builder prune
198
+
199
+ # Clean Docker volumes (will delete all unused volumes, including cache volumes)
200
+ docker volume prune
201
+
202
+ # Clean local cache directory
203
+ rm -rf .docker-cache
204
+ ```
205
+
206
+ ## Cross-Platform Compatibility
207
+
208
+ This project provides scripts for different operating systems:
209
+
210
+ 1. **Check Scripts**:
211
+ • `check_docker.sh` (macOS/Linux): Checks the Docker environment
212
+ • `check_docker.bat` (Windows): Checks the Docker environment
213
+
214
+ 2. **Build Scripts**:
215
+ • `build_docker.sh` (macOS/Linux): Builds the Docker image
216
+ • `build_docker.bat` (Windows): Builds the Docker image
217
+
218
+ 3. **Run Scripts**:
219
+ • `run_in_docker.sh` (macOS/Linux): Runs scripts in the Docker container
220
+ • `run_in_docker.bat` (Windows): Runs scripts in the Docker container
221
+
222
+ These scripts automatically detect the operating system type and use appropriate commands.
223
+
224
+ ## Troubleshooting
225
+
226
+ ### Container Fails to Start
227
+
228
+ Check the logs for more information:
229
+
230
+ ```bash
231
+ docker-compose logs
232
+ ```
233
+
234
+ ### API Key Issues
235
+
236
+ Ensure that you have correctly set all necessary API keys in the `owl/.env` file.
237
+
238
+ ### Docker Compose Warnings
239
+
240
+ If you see a warning about the `version` attribute being obsolete:
241
+
242
+ ```
243
+ WARN[0000] docker-compose.yml: the attribute `version` is obsolete
244
+ ```
245
+
246
+ This is because you are using Docker Compose v2.x, which no longer requires an explicit version number. We have removed this attribute from the configuration file, so you should no longer see this warning.
247
+
248
+ ### Browser-Related Issues
249
+
250
+ If you encounter browser-related issues, try the following solutions:
251
+
252
+ 1. Ensure that you are using the `xvfb-python` command to run Python scripts in the Docker container
253
+ 2. Check that Xvfb and related dependencies are correctly installed
254
+ 3. Increase the shared memory size (set to 2GB in docker-compose.yml)
255
+
256
+ ### Slow Build Speed
257
+
258
+ If the build speed is slow, try the following solutions:
259
+
260
+ 1. Ensure that Docker BuildKit is enabled (`DOCKER_BUILDKIT=1`)
261
+ 2. Ensure that pip caching is enabled (configured in docker-compose.yml)
262
+ 3. Use the `--build-arg BUILDKIT_INLINE_CACHE=1` parameter when building (configured in the build script)
263
+ 4. If this is the first build, downloading dependencies may take some time, but subsequent builds will be faster
264
+
265
+ ### Windows-Specific Issues
266
+
267
+ If you encounter issues on Windows:
268
+
269
+ 1. Ensure that you are running the Command Prompt or PowerShell with administrator privileges
270
+ 2. If you encounter path issues, try using forward slashes (/) instead of backslashes (\)
271
+ 3. If you encounter Docker Compose command issues, try using `docker compose` (without the hyphen)
272
+
273
+ ### Insufficient Memory
274
+
275
+ If you encounter insufficient memory issues, you can adjust resource limits in the `docker-compose.yml` file:
276
+
277
+ ```yaml
278
+ services:
279
+ owl:
280
+ # Other configurations...
281
+ deploy:
282
+ resources:
283
+ limits:
284
+ cpus: '4' # Increase CPU cores
285
+ memory: 8G # Increase memory limit
286
+ ```
287
+
288
+ ## Custom Docker Image
289
+
290
+ If you need to customize the Docker image, modify the `Dockerfile` file and then rebuild:
291
+
292
+ ```bash
293
+ # macOS/Linux
294
+ ./build_docker.sh
295
+
296
+ # Windows
297
+ build_docker.bat
298
+ ```
README.md ADDED
@@ -0,0 +1,311 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 align="center">
2
+ 🦉 OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation
3
+ </h1>
4
+
5
+
6
+ <div align="center">
7
+
8
+ [![Documentation][docs-image]][docs-url]
9
+ [![Discord][discord-image]][discord-url]
10
+ [![X][x-image]][x-url]
11
+ [![Reddit][reddit-image]][reddit-url]
12
+ [![Wechat][wechat-image]][wechat-url]
13
+ [![Wechat][owl-image]][owl-url]
14
+ [![Hugging Face][huggingface-image]][huggingface-url]
15
+ [![Star][star-image]][star-url]
16
+ [![Package License][package-license-image]][package-license-url]
17
+
18
+
19
+ </div>
20
+
21
+
22
+ <hr>
23
+
24
+ <div align="center">
25
+ <h4 align="center">
26
+
27
+ [中文阅读](https://github.com/camel-ai/owl/tree/main/README_zh.md) |
28
+ [Community](https://github.com/camel-ai/owl#community) |
29
+ [Installation](#️-installation) |
30
+ [Examples](https://github.com/camel-ai/owl/tree/main/owl) |
31
+ [Paper](https://arxiv.org/abs/2303.17760) |
32
+ [Citation](https://github.com/camel-ai/owl#citation) |
33
+ [Contributing](https://github.com/camel-ai/owl/graphs/contributors) |
34
+ [CAMEL-AI](https://www.camel-ai.org/)
35
+
36
+ </h4>
37
+
38
+ <div align="center" style="background-color: #f0f7ff; padding: 10px; border-radius: 5px; margin: 15px 0;">
39
+ <h3 style="color: #1e88e5; margin: 0;">
40
+ 🏆 OWL achieves <span style="color: #d81b60; font-weight: bold; font-size: 1.2em;">58.18</span> average score on GAIA benchmark and ranks <span style="color: #d81b60; font-weight: bold; font-size: 1.2em;">🏅️ #1</span> among open-source frameworks! 🏆
41
+ </h3>
42
+ </div>
43
+
44
+ <div align="center">
45
+
46
+ 🦉 OWL is a cutting-edge framework for multi-agent collaboration that pushes the boundaries of task automation, built on top of the [CAMEL-AI Framework](https://github.com/camel-ai/camel).
47
+
48
+ <!-- OWL achieves **58.18** average score on [GAIA](https://huggingface.co/spaces/gaia-benchmark/leaderboard) benchmark and ranks 🏅️ #1 among open-source frameworks. -->
49
+
50
+ Our vision is to revolutionize how AI agents collaborate to solve real-world tasks. By leveraging dynamic agent interactions, OWL enables more natural, efficient, and robust task automation across diverse domains.
51
+
52
+ </div>
53
+
54
+ ![](./assets/owl_architecture.png)
55
+
56
+ <br>
57
+
58
+
59
+ </div>
60
+
61
+ <!-- # Key Features -->
62
+ # 📋 Table of Contents
63
+
64
+ - [📋 Table of Contents](#-table-of-contents)
65
+ - [🔥 News](#-news)
66
+ - [🎬 Demo Video](#-demo-video)
67
+ - [✨️ Core Features](#-core-features)
68
+ - [🛠️ Installation](#️-installation)
69
+ - [**Clone the Github repository**](#clone-the-github-repository)
70
+ - [**Set up Environment**](#set-up-environment)
71
+ - [**Install Dependencies**](#install-dependencies)
72
+ - [**Setup Environment Variables**](#setup-environment-variables)
73
+ - [**Running with Docker**](#running-with-docker)
74
+
75
+ - [🚀 Quick Start](#-quick-start)
76
+ - [🧪 Experiments](#-experiments)
77
+ - [⏱️ Future Plans](#️-future-plans)
78
+ - [📄 License](#-license)
79
+ - [🖊️ Cite](#️-cite)
80
+ - [🔥 Community](#-community)
81
+ - [❓ FAQ](#-faq)
82
+ - [⭐ Star History](#-star-history)
83
+
84
+
85
+ # 🔥 News
86
+
87
+ - **[2025.03.07]**: We open-source the codebase of 🦉 OWL project.
88
+
89
+ # 🎬 Demo Video
90
+
91
+ https://private-user-images.githubusercontent.com/55657767/420211368-f29f477d-7eef-46da-8d7a-8f3bcf506da2.mp4
92
+
93
+ https://private-user-images.githubusercontent.com/55657767/420212194-e813fc05-136a-485f-8df3-f10d9b4e63ec.mp4
94
+
95
+ # ✨️ Core Features
96
+
97
+ - **Real-time Information Retrieval**: Leverage Wikipedia, Google Search, and other online sources for up-to-date information.
98
+ - **Multimodal Processing**: Support for handling internet or local videos, images, and audio data.
99
+ - **Browser Automation**: Utilize the Playwright framework for simulating browser interactions, including scrolling, clicking, input handling, downloading, navigation, and more.
100
+ - **Document Parsing**: Extract content from Word, Excel, PDF, and PowerPoint files, converting them into text or Markdown format.
101
+ - **Code Execution**: Write and execute Python code using interpreter.
102
+
103
+ # 🛠️ Installation
104
+
105
+ ## **Clone the Github repository**
106
+
107
+ ```bash
108
+ git clone https://github.com/camel-ai/owl.git
109
+ cd owl
110
+ ```
111
+
112
+ ## **Set up Environment**
113
+
114
+ Using Conda (recommended):
115
+ ```bash
116
+ conda create -n owl python=3.11
117
+ conda activate owl
118
+ ```
119
+
120
+ Using venv (alternative):
121
+ ```bash
122
+ python -m venv owl_env
123
+ # On Windows
124
+ owl_env\Scripts\activate
125
+ # On Unix or MacOS
126
+ source owl_env/bin/activate
127
+ ```
128
+
129
+
130
+ ## **Install Dependencies**
131
+
132
+ ```bash
133
+ python -m pip install -r requirements.txt
134
+ playwright install
135
+ ```
136
+
137
+ ## **Setup Environment Variables**
138
+
139
+ In the `owl/.env_template` file, you will find all the necessary API keys along with the websites where you can register for each service. To use these API services, follow these steps:
140
+
141
+ 1. *Copy and Rename*: Duplicate the `.env_example` file and rename the copy to `.env`.
142
+ ```bash
143
+ cp owl/.env_template .env
144
+ ```
145
+ 2. *Fill in Your Keys*: Open the `.env` file and insert your API keys in the corresponding fields. (For the minimal example (`run_mini.py`), you only need to configure the LLM API key (e.g., OPENAI_API_KEY).)
146
+ 3. *For using more other models*: please refer to our CAMEL models docs:https://docs.camel-ai.org/key_modules/models.html#supported-model-platforms-in-camel
147
+
148
+
149
+ > **Note**: For optimal performance, we strongly recommend using OpenAI models. Our experiments show that other models may result in significantly lower performance on complex tasks and benchmarks.
150
+
151
+ ## **Running with Docker**
152
+
153
+ If you prefer to run the OWL project using Docker, we provide full Docker support:
154
+
155
+ ```bash
156
+ # Clone the repository
157
+ git clone https://github.com/camel-ai/owl.git
158
+ cd owl
159
+
160
+ # Configure environment variables
161
+ cp owl/.env_template owl/.env
162
+ # Edit the .env file and fill in your API keys
163
+
164
+ # Build and run the Docker container
165
+ docker-compose up -d
166
+
167
+ # Run OWL inside the container
168
+ docker-compose exec owl bash -c "xvfb-python run.py"
169
+ ```
170
+
171
+ For more detailed Docker usage instructions, including cross-platform support, optimized configurations, and troubleshooting, please refer to [DOCKER_README.md](DOCKER_README_en.md).
172
+
173
+ # 🚀 Quick Start
174
+
175
+
176
+
177
+ Run the following demo case:
178
+
179
+ ```bash
180
+ python owl/run.py
181
+ ```
182
+
183
+ ## Running with Different Models
184
+
185
+ OWL supports various LLM backends. You can use the following scripts to run with different models:
186
+
187
+ ```bash
188
+ # Run with Qwen model
189
+ python owl/run_qwen.py
190
+
191
+ # Run with Deepseek model
192
+ python owl/run_deepseek.py
193
+
194
+ # Run with other OpenAI-compatible models
195
+ python owl/run_openai_compatiable_model.py
196
+ ```
197
+
198
+ For a simpler version that only requires an LLM API key, you can try our minimal example:
199
+
200
+ ```bash
201
+ python owl/run_mini.py
202
+ ```
203
+
204
+ You can run OWL agent with your own task by modifying the `run.py` script:
205
+
206
+ ```python
207
+ # Define your own task
208
+ question = "Task description here."
209
+
210
+ society = construct_society(question)
211
+ answer, chat_history, token_count = run_society(society)
212
+
213
+ print(f"Answer: {answer}")
214
+ ```
215
+
216
+ For uploading files, simply provide the file path along with your question:
217
+
218
+ ```python
219
+ # Task with a local file (e.g., file path: `tmp/example.docx`)
220
+ question = "What is in the given DOCX file? Here is the file path: tmp/example.docx"
221
+
222
+ society = construct_society(question)
223
+ answer, chat_history, token_count = run_society(society)
224
+ print(f"Answer: {answer}")
225
+ ```
226
+
227
+ OWL will then automatically invoke document-related tools to process the file and extract the answer.
228
+
229
+
230
+ Example tasks you can try:
231
+ - "Find the latest stock price for Apple Inc."
232
+ - "Analyze the sentiment of recent tweets about climate change"
233
+ - "Help me debug this Python code: [your code here]"
234
+ - "Summarize the main points from this research paper: [paper URL]"
235
+
236
+ # 🧪 Experiments
237
+
238
+ We provided a script to reproduce the results on GAIA.
239
+ You can check the `run_gaia_roleplaying.py` file and run the following command:
240
+
241
+ ```bash
242
+ python run_gaia_roleplaying.py
243
+ ```
244
+
245
+ # ⏱️ Future Plans
246
+
247
+ - [ ] Write a technical blog post detailing our exploration and insights in multi-agent collaboration in real-world tasks.
248
+ - [ ] Enhance the toolkit ecosystem with more specialized tools for domain-specific tasks.
249
+ - [ ] Develop more sophisticated agent interaction patterns and communication protocols
250
+
251
+
252
+ # 📄 License
253
+
254
+ The source code is licensed under Apache 2.0.
255
+
256
+ # 🖊️ Cite
257
+
258
+ If you find this repo useful, please cite:
259
+
260
+
261
+ ```
262
+ @misc{owl2025,
263
+ title = {OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation},
264
+ author = {{CAMEL-AI.org}},
265
+ howpublished = {\url{https://github.com/camel-ai/owl}},
266
+ note = {Accessed: 2025-03-07},
267
+ year = {2025}
268
+ }
269
+ ```
270
+
271
+ # 🔥 Community
272
+ Join us for further discussions!
273
+ <!-- ![](./assets/community.png) -->
274
+ ![](./assets/community_5.jpg)
275
+ <!-- ![](./assets/meetup.jpg) -->
276
+
277
+ # ❓ FAQ
278
+
279
+ **Q: Why don't I see Chrome running locally after starting the example script?**
280
+
281
+ A: If OWL determines that a task can be completed using non-browser tools (such as search or code execution), the browser will not be launched. The browser window will only appear when OWL determines that browser-based interaction is necessary.
282
+
283
+ # ⭐ Star History
284
+
285
+ [![Star History Chart](https://api.star-history.com/svg?repos=camel-ai/owl&type=Date)](https://star-history.com/#camel-ai/owl&Date)
286
+
287
+
288
+
289
+ [docs-image]: https://img.shields.io/badge/Documentation-EB3ECC
290
+ [docs-url]: https://camel-ai.github.io/camel/index.html
291
+ [star-image]: https://img.shields.io/github/stars/camel-ai/owl?label=stars&logo=github&color=brightgreen
292
+ [star-url]: https://github.com/camel-ai/owl/stargazers
293
+ [package-license-image]: https://img.shields.io/badge/License-Apache_2.0-blue.svg
294
+ [package-license-url]: https://github.com/camel-ai/owl/blob/main/licenses/LICENSE
295
+
296
+ [colab-url]: https://colab.research.google.com/drive/1AzP33O8rnMW__7ocWJhVBXjKziJXPtim?usp=sharing
297
+ [colab-image]: https://colab.research.google.com/assets/colab-badge.svg
298
+ [huggingface-url]: https://huggingface.co/camel-ai
299
+ [huggingface-image]: https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-CAMEL--AI-ffc107?color=ffc107&logoColor=white
300
+ [discord-url]: https://discord.camel-ai.org/
301
+ [discord-image]: https://img.shields.io/discord/1082486657678311454?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb
302
+ [wechat-url]: https://ghli.org/camel/wechat.png
303
+ [wechat-image]: https://img.shields.io/badge/WeChat-CamelAIOrg-brightgreen?logo=wechat&logoColor=white
304
+ [x-url]: https://x.com/CamelAIOrg
305
+ [x-image]: https://img.shields.io/twitter/follow/CamelAIOrg?style=social
306
+ [twitter-image]: https://img.shields.io/twitter/follow/CamelAIOrg?style=social&color=brightgreen&logo=twitter
307
+ [reddit-url]: https://www.reddit.com/r/CamelAI/
308
+ [reddit-image]: https://img.shields.io/reddit/subreddit-subscribers/CamelAI?style=plastic&logo=reddit&label=r%2FCAMEL&labelColor=white
309
+ [ambassador-url]: https://www.camel-ai.org/community
310
+ [owl-url]: ./assets/qr_code.jpg
311
+ [owl-image]: https://img.shields.io/badge/WeChat-OWLProject-brightgreen?logo=wechat&logoColor=white
README_zh.md ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 align="center">
2
+ 🦉 OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation
3
+ 🦉 OWL: 优化劳动力学习的通用智能体,用于处理现实世界的自动化任务
4
+ </h1>
5
+
6
+
7
+ <div align="center">
8
+
9
+ [![文档][docs-image]][docs-url]
10
+ [![Discord][discord-image]][discord-url]
11
+ [![X][x-image]][x-url]
12
+ [![Reddit][reddit-image]][reddit-url]
13
+ [![微信][wechat-image]][wechat-url]
14
+ [![微信][owl-image]][owl-url]
15
+ [![Hugging Face][huggingface-image]][huggingface-url]
16
+ [![Star][star-image]][star-url]
17
+ [![软件许可证][package-license-image]][package-license-url]
18
+
19
+
20
+ </div>
21
+
22
+
23
+ <hr>
24
+
25
+ <div align="center">
26
+ <h4 align="center">
27
+
28
+ [English README](https://github.com/camel-ai/owl/tree/main) |
29
+ [社区](https://github.com/camel-ai/camel#community) |
30
+ [安装](#️-installation) |
31
+ [示例](https://github.com/camel-ai/owl/tree/main/owl) |
32
+ [论文](https://arxiv.org/abs/2303.17760) |
33
+ [引用](#-community) |
34
+ [贡献](https://github.com/camel-ai/owl/graphs/contributors) |
35
+ [CAMEL-AI](https://www.camel-ai.org/)
36
+
37
+ </h4>
38
+
39
+ <div align="center" style="background-color: #f0f7ff; padding: 10px; border-radius: 5px; margin: 15px 0;">
40
+ <h3 style="color: #1e88e5; margin: 0;">
41
+ 🏆 OWL 在 GAIA 基准测试中取得 <span style="color: #d81b60; font-weight: bold; font-size: 1.2em;">58.18</span> 平均分,在开源框架中排名 <span style="color: #d81b60; font-weight: bold; font-size: 1.2em;">🏅️ #1</span>! 🏆
42
+ </h3>
43
+ </div>
44
+
45
+ <div align="center">
46
+
47
+ 🦉 OWL 是一个前沿的多智能体协作框架,推动任务自动化的边界,构建在 [CAMEL-AI Framework](https://github.com/camel-ai/camel)。
48
+
49
+ 我们的愿景是彻底变革 AI 智能体协作解决现实任务的方式。通过利用动态智能体交互,OWL 实现了跨多领域更自然、高效且稳健的任务自动化。
50
+
51
+ </div>
52
+
53
+ ![](./assets/owl_architecture.png)
54
+
55
+
56
+
57
+ <br>
58
+
59
+
60
+ </div>
61
+
62
+ <!-- # Key Features -->
63
+ # 📋 目录
64
+
65
+ - [📋 目录](#-目录)
66
+ - [🔥 新闻](#-新闻)
67
+ - [🎬 演示视频](#-演示视频)
68
+ - [✨️ 核心功能](#-核心功能)
69
+ - [🛠️ 安装](#️-安装)
70
+ - [**克隆 Github 仓库**](#克隆-github-仓库)
71
+ - [**设置环境**](#设置环境)
72
+ - [**安装依赖**](#安装依赖)
73
+ - [**设置环境变量**](#设置环境变量)
74
+ - [**使用Docker运行**](#使用docker运行)
75
+ - [🚀 快速开始](#-快速开始)
76
+ - [🧪 实验](#-实验)
77
+ - [⏱️ 未来计划](#️-未来计划)
78
+ - [📄 许可证](#-许可证)
79
+ - [🖊️ 引用](#️-引用)
80
+ - [🔥 社区](#-社区)
81
+ - [❓ 常见问题](#-常见问题)
82
+
83
+
84
+ # 🔥 新闻
85
+
86
+ - **[2025.03.07]**: 我们开源了 🦉 OWL 项目的代码库。
87
+
88
+ # 🎬 演示视频
89
+
90
+ https://private-user-images.githubusercontent.com/55657767/420211368-f29f477d-7eef-46da-8d7a-8f3bcf506da2.mp4
91
+
92
+ https://private-user-images.githubusercontent.com/55657767/420212194-e813fc05-136a-485f-8df3-f10d9b4e63ec.mp4
93
+
94
+ # ✨️ 核心功能
95
+
96
+ - **在线搜索**:使用维基百科、谷歌搜索等,进行实时信息检索
97
+ - **多模态处理**:支持互联网或本地视频、图片、语音处理
98
+ - **浏览器操作**:借助Playwright框架开发浏览器模拟交互,支持页面滚动、点击、输入、下载、历史回退等功能
99
+ - **文件解析**:word、excel、PDF、PowerPoint信息提取,内容转文本/Markdown
100
+ - **代码执行**:编写python代码,并使用解释器运行
101
+
102
+ # 🛠️ 安装
103
+
104
+ ## **克隆 Github 仓库**
105
+
106
+ ```bash
107
+ git clone https://github.com/camel-ai/owl.git
108
+ cd owl
109
+ ```
110
+
111
+ ## **设置环境**
112
+
113
+ 使用 Conda(推荐):
114
+ ```bash
115
+ conda create -n owl python=3.11
116
+ conda activate owl
117
+ ```
118
+
119
+ 使用 venv(备用):
120
+ ```bash
121
+ python -m venv owl_env
122
+ # Windows 系统
123
+ owl_env\Scripts\activate
124
+ # Unix 或 MacOS 系统
125
+ source owl_env/bin/activate
126
+ ```
127
+
128
+ ## **安装依赖**
129
+
130
+ ```bash
131
+ python -m pip install -r requirements.txt
132
+ ```
133
+
134
+ ## **设置环境变量**
135
+
136
+ 在 `owl/.env_template` 文件中,你可以找到所有必要的 API 密钥以及各服务的注册网址。要使用这些 API 服务,请按照以下步骤操作:
137
+
138
+ 1. *复制并重命名*: 复制 `.env_example` 文件,并将副本重命名为 `.env`。
139
+ 2. *填写你的密钥*: 打开 `.env` 文件,在相应字段中填入你的 API 密钥。
140
+ 3. *如需使用更多其他模型*:请参考我们CAMEL的models文档:https://docs.camel-ai.org/key_modules/models.html#supported-model-platforms-in-camel
141
+
142
+ > **注意**:为获得最佳性能,我们强烈建议使用 OpenAI 模型。我们通过测试发现,其他模型在处理复杂任务和基准测试时可能会导致性能显著降低。
143
+
144
+ ## **使用Docker运行**
145
+
146
+ 如果您希望使用Docker运行OWL项目,我们提供了完整的Docker支持:
147
+
148
+ ```bash
149
+ # 克隆仓库
150
+ git clone https://github.com/camel-ai/owl.git
151
+ cd owl
152
+
153
+ # 配置环境变量
154
+ cp owl/.env_template owl/.env
155
+ # 编辑.env文件,填入您的API密钥
156
+
157
+ # 构建并运行Docker容器
158
+ docker-compose up -d
159
+
160
+ # 在容器中���行OWL
161
+ docker-compose exec owl bash -c "xvfb-python run.py"
162
+ ```
163
+
164
+ 更多详细的Docker使用说明,包括跨平台支持、优化配置和故障排除,请参阅 [DOCKER_README.md](DOCKER_README.md)
165
+
166
+ # 🚀 快速开始
167
+
168
+ 运行以下示例:
169
+
170
+ ```bash
171
+ python owl/run.py
172
+ ```
173
+
174
+ 我们还提供了一个最小化示例,只需配置LLM的API密钥即可运行:
175
+
176
+ ```bash
177
+ python owl/run_mini.py
178
+ ```
179
+
180
+ ## 使用不同的模型
181
+
182
+ OWL 支持多种 LLM 后端。您可以使用以下脚本来运行不同的模型:
183
+
184
+ ```bash
185
+ # 使用 Qwen 模型运行
186
+ python owl/run_qwen.py
187
+
188
+ # 使用 Deepseek 模型运行
189
+ python owl/run_deepseek.py
190
+
191
+ # 使用其他 OpenAI 兼容模型运行
192
+ python owl/run_openai_compatiable_model.py
193
+ ```
194
+
195
+ 你可以通过修改 `run.py` 脚本来运行自己的任务:
196
+
197
+ ```python
198
+ # Define your own task
199
+ question = "Task description here."
200
+
201
+ society = construct_society(question)
202
+ answer, chat_history, token_count = run_society(society)
203
+
204
+ print(f"Answer: {answer}")
205
+ ```
206
+
207
+ 上传文件时,只需提供文件路径和问题:
208
+
209
+ ```python
210
+ # 处理本地文件(例如,文件路径为 `tmp/example.docx`)
211
+ question = "给定的 DOCX 文件中有什么内容?文件路径如下:tmp/example.docx"
212
+
213
+ society = construct_society(question)
214
+ answer, chat_history, token_count = run_society(society)
215
+
216
+ print(f"答案:{answer}")
217
+ ```
218
+
219
+ OWL 将自动调用与文档相关的工具来处理文件并提取答案。
220
+
221
+ 你可以尝试以下示例任务:
222
+ - "查询苹果公司的最新股票价格"
223
+ - "分析关于气候变化的最新推文情绪"
224
+ - "帮我调试这段 Python 代码:[在此粘贴你的代码]"
225
+ - "总结这篇研究论文的主要观点:[论文URL]"
226
+
227
+ # 🧪 实验
228
+
229
+ 我们提供了一个脚本用于复现 GAIA 上的实验结果。
230
+ 你可以查看 `run_gaia_roleplaying.py` 文件,并运行以下命令:
231
+
232
+ ```bash
233
+ python run_gaia_roleplaying.py
234
+ ```
235
+
236
+ # ⏱️ 未来计划
237
+
238
+ - [ ] 撰写一篇技术博客,详细介绍我们在现实任务中多智能体协作方面的探索与见解。
239
+ - [ ] 通过引入更多针对特定领域任务的专业工具,进一步完善工具生态系统。
240
+ - [ ] 开发更复杂的智能体交互模式和通信协议
241
+
242
+
243
+ # 📄 许可证
244
+
245
+ 源代码采用 Apache 2.0 许可证。
246
+
247
+ # 🖊️ 引用
248
+
249
+ 如果你觉得这个仓库对你有帮助,请引用:
250
+
251
+
252
+ ```
253
+ @misc{owl2025,
254
+ title = {OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation},
255
+ author = {{CAMEL-AI.org}},
256
+ howpublished = {\url{https://github.com/camel-ai/owl}},
257
+ note = {Accessed: 2025-03-07},
258
+ year = {2025}
259
+ }
260
+ ```
261
+
262
+ # 🔥 社区
263
+ 加入我们,参与更多讨论!
264
+ <!-- ![](./assets/community.png) -->
265
+ ![](./assets/community_5.jpg)
266
+ <!-- ![](./assets/meetup.jpg) -->
267
+
268
+ # ❓ 常见问题
269
+
270
+ **Q: 为什么启动示例脚本后,我没有看到本地运行Chrome浏览器?**
271
+
272
+ A: 当OWL判断某个任务可以使用非浏览器工具(如搜索、代码分析等)完成时,浏览器就不会启动。只有在判断需要使用浏览器工具的时候,本地才会弹出浏览器窗口,并进行浏览器模拟交互。
273
+
274
+ [docs-image]: https://img.shields.io/badge/Documentation-EB3ECC
275
+ [docs-url]: https://camel-ai.github.io/camel/index.html
276
+ [star-image]: https://img.shields.io/github/stars/camel-ai/owl?label=stars&logo=github&color=brightgreen
277
+ [star-url]: https://github.com/camel-ai/owl/stargazers
278
+ [package-license-image]: https://img.shields.io/badge/License-Apache_2.0-blue.svg
279
+ [package-license-url]: https://github.com/camel-ai/owl/blob/main/licenses/LICENSE
280
+
281
+ [colab-url]: https://colab.research.google.com/drive/1AzP33O8rnMW__7ocWJhVBXjKziJXPtim?usp=sharing
282
+ [colab-image]: https://colab.research.google.com/assets/colab-badge.svg
283
+ [huggingface-url]: https://huggingface.co/camel-ai
284
+ [huggingface-image]: https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-CAMEL--AI-ffc107?color=ffc107&logoColor=white
285
+ [discord-url]: https://discord.camel-ai.org/
286
+ [discord-image]: https://img.shields.io/discord/1082486657678311454?logo=discord&labelColor=%20%235462eb&logoColor=%20%23f5f5f5&color=%20%235462eb
287
+ [wechat-url]: https://ghli.org/camel/wechat.png
288
+ [wechat-image]: https://img.shields.io/badge/WeChat-CamelAIOrg-brightgreen?logo=wechat&logoColor=white
289
+ [x-url]: https://x.com/CamelAIOrg
290
+ [x-image]: https://img.shields.io/twitter/follow/CamelAIOrg?style=social
291
+ [twitter-image]: https://img.shields.io/twitter/follow/CamelAIOrg?style=social&color=brightgreen&logo=twitter
292
+ [reddit-url]: https://www.reddit.com/r/CamelAI/
293
+ [reddit-image]: https://img.shields.io/reddit/subreddit-subscribers/CamelAI?style=plastic&logo=reddit&label=r%2FCAMEL&labelColor=white
294
+ [ambassador-url]: https://www.camel-ai.org/community
295
+ [owl-url]: ./assets/qr_code.jpg
296
+ [owl-image]: https://img.shields.io/badge/WeChat-OWLProject-brightgreen?logo=wechat&logoColor=white
assets/community.png ADDED

Git LFS Details

  • SHA256: fddbef8b353483d12e2b4a34f09f984a15a40aef67952800d904dc56f3f9ec1c
  • Pointer size: 131 Bytes
  • Size of remote file: 525 kB
assets/community_2.png ADDED

Git LFS Details

  • SHA256: 38b15dddd4d15a09693b580d8eb8ea8648d7538e2a40c38aaf5d9e96f5d623c3
  • Pointer size: 131 Bytes
  • Size of remote file: 515 kB
assets/community_3.jpg ADDED

Git LFS Details

  • SHA256: 93ea8d0fd991bf28adfb4d139e58677d03c58a396efca0a368a011262ffca544
  • Pointer size: 131 Bytes
  • Size of remote file: 522 kB
assets/community_4.jpg ADDED

Git LFS Details

  • SHA256: b6cc11f29ca7658ac796d88921ce56a41f29326fc6d2df10c0d37a8e9492b79a
  • Pointer size: 131 Bytes
  • Size of remote file: 514 kB
assets/community_5.jpg ADDED

Git LFS Details

  • SHA256: 4e3351a2cb11a36784440bfb03c2034df2de4ae8387939b8c08f7f292d3c9669
  • Pointer size: 131 Bytes
  • Size of remote file: 524 kB
assets/meetup.jpg ADDED

Git LFS Details

  • SHA256: aab18b1136a0bb0d0950b6f56142f56bf2e6ba361086ae45b3e4e252fe889d7d
  • Pointer size: 131 Bytes
  • Size of remote file: 458 kB
assets/owl_architecture.png ADDED

Git LFS Details

  • SHA256: 91329979fa41a853e4275394a18d963599f1111d61f60ad1fb7dddaf0d31b1bb
  • Pointer size: 131 Bytes
  • Size of remote file: 591 kB
assets/qr_code.jpg ADDED

Git LFS Details

  • SHA256: 3d7de24cf23f9777b7893468f0b52cba0628d89c62fa0e1aa218582f202a9806
  • Pointer size: 131 Bytes
  • Size of remote file: 161 kB
licenses/LICENSE ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
licenses/license_template.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
licenses/update_license.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import os
15
+ import re
16
+ import sys
17
+ from pathlib import Path
18
+ from typing import List
19
+
20
+
21
+ # The license template file is hard-coded with specific start and end lines
22
+ def fine_license_start_line(lines: List[str], start_with: str) -> int:
23
+ for i in range(len(lines)):
24
+ if lines[i].startswith(start_with):
25
+ return i
26
+ return None
27
+
28
+
29
+ def find_license_end_line(lines: List[str], start_with: str) -> int:
30
+ for i in range(len(lines) - 1, -1, -1):
31
+ if lines[i].startswith(start_with):
32
+ return i
33
+ return None
34
+
35
+
36
+ def update_license_in_file(
37
+ file_path: str,
38
+ license_template_path: str,
39
+ start_line_start_with: str,
40
+ end_line_start_with: str,
41
+ ) -> bool:
42
+ with open(
43
+ file_path, 'r', encoding='utf-8'
44
+ ) as f: # for windows compatibility
45
+ content = f.read()
46
+
47
+ with open(license_template_path, 'r', encoding='utf-8') as f:
48
+ new_license = f.read().strip()
49
+
50
+ maybe_existing_licenses = re.findall(
51
+ r'^#.*?(?=\n)', content, re.MULTILINE | re.DOTALL
52
+ )
53
+ start_index = fine_license_start_line(
54
+ maybe_existing_licenses, start_line_start_with
55
+ )
56
+ end_index = find_license_end_line(
57
+ maybe_existing_licenses, end_line_start_with
58
+ )
59
+ if start_index is not None and end_index is not None:
60
+ maybe_existing_licenses = maybe_existing_licenses[
61
+ start_index : end_index + 1
62
+ ]
63
+ else:
64
+ maybe_existing_licenses = None
65
+ if maybe_existing_licenses:
66
+ maybe_old_licenses = '\n'.join(maybe_existing_licenses)
67
+ if maybe_old_licenses.strip() != new_license.strip():
68
+ replaced_content = content.replace(maybe_old_licenses, new_license)
69
+ with open(file_path, 'w') as f:
70
+ f.write(replaced_content)
71
+ print(f'Replaced license in {file_path}')
72
+ return True
73
+ else:
74
+ return False
75
+ else:
76
+ with open(file_path, 'w') as f:
77
+ f.write(new_license + '\n' + content)
78
+ print(f'Added license to {file_path}')
79
+ return True
80
+
81
+
82
+ def update_license_in_directory(
83
+ directory_path: str,
84
+ license_template_path: str,
85
+ start_line_start_with: str,
86
+ end_line_start_with: str,
87
+ ) -> None:
88
+ # Check if directory exists
89
+ if not os.path.isdir(directory_path):
90
+ raise NotADirectoryError(f'{directory_path} is not a directory')
91
+ # Check if license template exists
92
+ if not os.path.isfile(license_template_path):
93
+ raise FileNotFoundError(f'{license_template_path} not found')
94
+
95
+ file_count = 0
96
+ for py_files in Path(directory_path).rglob("*.py"):
97
+ if py_files.name.startswith('.'):
98
+ continue
99
+ if any(part.startswith('.') for part in py_files.parts):
100
+ continue
101
+ if update_license_in_file(
102
+ py_files,
103
+ license_template_path,
104
+ start_line_start_with,
105
+ end_line_start_with,
106
+ ):
107
+ file_count += 1
108
+
109
+ print(f'License updated in {file_count} files')
110
+
111
+
112
+ if __name__ == '__main__':
113
+ if len(sys.argv) < 3:
114
+ print(
115
+ "Usage from command line: "
116
+ "python update_license.py <directory_path> <license_template_path>"
117
+ "No valid input arguments found, please enter manually."
118
+ )
119
+ directory_path = input("Enter directory path: ")
120
+ license_template_path = input("Enter license template path: ")
121
+ else:
122
+ directory_path = sys.argv[1]
123
+ license_template_path = sys.argv[2]
124
+
125
+ start_line_start_with = "# ========= Copyright"
126
+ end_line_start_with = "# ========= Copyright"
127
+ update_license_in_directory(
128
+ directory_path,
129
+ license_template_path,
130
+ start_line_start_with,
131
+ end_line_start_with,
132
+ )
owl/.env_template ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MODEL & API (See https://github.com/camel-ai/camel/blob/master/camel/types/enums.py)
2
+
3
+ # OPENAI API
4
+ OPENAI_API_KEY = ""
5
+ # OPENAI_API_BASE_URL = ""
6
+
7
+ # Qwen API (https://help.aliyun.com/zh/model-studio/developer-reference/get-api-key)
8
+ # QWEN_API_KEY=""
9
+
10
+ # DeepSeek API (https://platform.deepseek.com/api_keys)
11
+ # DEEPSEEK_API_KEY=""
12
+
13
+ #===========================================
14
+ # Tools & Services API
15
+ #===========================================
16
+
17
+ # Google Search API (https://developers.google.com/custom-search/v1/overview)
18
+ GOOGLE_API_KEY=""
19
+ SEARCH_ENGINE_ID=""
20
+
21
+ # Hugging Face API (https://huggingface.co/join)
22
+ HF_TOKEN=""
23
+
24
+ # Chunkr API (https://chunkr.ai/)
25
+ CHUNKR_API_KEY=""
26
+
27
+ # Firecrawl API (https://www.firecrawl.dev/)
28
+ FIRECRAWL_API_KEY=""
owl/camel/__init__.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ from camel.logger import disable_logging, enable_logging, set_log_level
16
+
17
+ __version__ = '0.2.11'
18
+
19
+ __all__ = [
20
+ '__version__',
21
+ 'camel',
22
+ 'disable_logging',
23
+ 'enable_logging',
24
+ 'set_log_level',
25
+ ]
owl/camel/agents/__init__.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from .base import BaseAgent
15
+ from .chat_agent import ChatAgent
16
+ from .critic_agent import CriticAgent
17
+ from .embodied_agent import EmbodiedAgent
18
+ from .knowledge_graph_agent import KnowledgeGraphAgent
19
+ from .role_assignment_agent import RoleAssignmentAgent
20
+ from .search_agent import SearchAgent
21
+ from .task_agent import (
22
+ TaskCreationAgent,
23
+ TaskPlannerAgent,
24
+ TaskPrioritizationAgent,
25
+ TaskSpecifyAgent,
26
+ )
27
+ from .tool_agents.base import BaseToolAgent
28
+ from .tool_agents.hugging_face_tool_agent import HuggingFaceToolAgent
29
+
30
+ __all__ = [
31
+ 'BaseAgent',
32
+ 'ChatAgent',
33
+ 'TaskSpecifyAgent',
34
+ 'TaskPlannerAgent',
35
+ 'TaskCreationAgent',
36
+ 'TaskPrioritizationAgent',
37
+ 'CriticAgent',
38
+ 'BaseToolAgent',
39
+ 'HuggingFaceToolAgent',
40
+ 'EmbodiedAgent',
41
+ 'RoleAssignmentAgent',
42
+ 'SearchAgent',
43
+ 'KnowledgeGraphAgent',
44
+ ]
owl/camel/agents/base.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from abc import ABC, abstractmethod
15
+ from typing import Any
16
+
17
+
18
+ class BaseAgent(ABC):
19
+ r"""An abstract base class for all CAMEL agents."""
20
+
21
+ @abstractmethod
22
+ def reset(self, *args: Any, **kwargs: Any) -> Any:
23
+ r"""Resets the agent to its initial state."""
24
+ pass
25
+
26
+ @abstractmethod
27
+ def step(self, *args: Any, **kwargs: Any) -> Any:
28
+ r"""Performs a single step of the agent."""
29
+ pass
owl/camel/agents/chat_agent.py ADDED
@@ -0,0 +1,1411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ # import logging
18
+ import re
19
+ import uuid
20
+ from collections import defaultdict
21
+ from typing import (
22
+ TYPE_CHECKING,
23
+ Any,
24
+ Callable,
25
+ Dict,
26
+ List,
27
+ Optional,
28
+ Tuple,
29
+ Type,
30
+ Union,
31
+ )
32
+
33
+ from loguru import logger
34
+
35
+ from openai.types.chat import ChatCompletionMessageToolCall
36
+ from openai.types.chat.chat_completion_message_tool_call import Function
37
+ from pydantic import BaseModel
38
+
39
+ from camel.agents.base import BaseAgent
40
+ from camel.memories import (
41
+ AgentMemory,
42
+ ChatHistoryMemory,
43
+ MemoryRecord,
44
+ ScoreBasedContextCreator,
45
+ )
46
+ from camel.messages import BaseMessage, FunctionCallingMessage, OpenAIMessage
47
+ from camel.models import (
48
+ BaseModelBackend,
49
+ ModelFactory,
50
+ ModelManager,
51
+ ModelProcessingError,
52
+ )
53
+ from camel.responses import ChatAgentResponse
54
+ from camel.types import (
55
+ ChatCompletion,
56
+ ChatCompletionChunk,
57
+ ModelPlatformType,
58
+ ModelType,
59
+ OpenAIBackendRole,
60
+ RoleType,
61
+ )
62
+ from camel.utils import (
63
+ func_string_to_callable,
64
+ get_model_encoding,
65
+ get_pydantic_object_schema,
66
+ json_to_function_code,
67
+ )
68
+
69
+ if TYPE_CHECKING:
70
+ from openai import Stream
71
+
72
+ from camel.terminators import ResponseTerminator
73
+ from camel.toolkits import FunctionTool
74
+
75
+
76
+ # logger = logging.getLogger(__name__)
77
+
78
+ # AgentOps decorator setting
79
+ try:
80
+ import os
81
+
82
+ if os.getenv("AGENTOPS_API_KEY") is not None:
83
+ from agentops import track_agent
84
+ else:
85
+ raise ImportError
86
+ except (ImportError, AttributeError):
87
+ from camel.utils import track_agent
88
+
89
+
90
+ class FunctionCallingRecord(BaseModel):
91
+ r"""Historical records of functions called in the conversation.
92
+
93
+ Attributes:
94
+ func_name (str): The name of the function being called.
95
+ args (Dict[str, Any]): The dictionary of arguments passed to
96
+ the function.
97
+ result (Any): The execution result of calling this function.
98
+ """
99
+
100
+ func_name: str
101
+ args: Dict[str, Any]
102
+ result: Any
103
+
104
+ def __str__(self) -> str:
105
+ r"""Overridden version of the string function.
106
+
107
+ Returns:
108
+ str: Modified string to represent the function calling.
109
+ """
110
+ return (
111
+ f"Function Execution: {self.func_name}\n"
112
+ f"\tArgs: {self.args}\n"
113
+ f"\tResult: {self.result}"
114
+ )
115
+
116
+ def as_dict(self) -> dict[str, Any]:
117
+ r"""Returns the function calling record as a dictionary.
118
+
119
+ Returns:
120
+ dict[str, Any]: The function calling record as a dictionary.
121
+ """
122
+ return self.model_dump()
123
+
124
+
125
+ @track_agent(name="ChatAgent")
126
+ class ChatAgent(BaseAgent):
127
+ r"""Class for managing conversations of CAMEL Chat Agents.
128
+
129
+ Args:
130
+ system_message (Union[BaseMessage, str], optional): The system message
131
+ for the chat agent.
132
+ model (BaseModelBackend, optional): The model backend to use for
133
+ generating responses. (default: :obj:`ModelPlatformType.DEFAULT`
134
+ with `ModelType.DEFAULT`)
135
+ memory (AgentMemory, optional): The agent memory for managing chat
136
+ messages. If `None`, a :obj:`ChatHistoryMemory` will be used.
137
+ (default: :obj:`None`)
138
+ message_window_size (int, optional): The maximum number of previous
139
+ messages to include in the context window. If `None`, no windowing
140
+ is performed. (default: :obj:`None`)
141
+ token_limit (int, optional): The maximum number of tokens in a context.
142
+ The context will be automatically pruned to fulfill the limitation.
143
+ If `None`, it will be set according to the backend model.
144
+ (default: :obj:`None`)
145
+ output_language (str, optional): The language to be output by the
146
+ agent. (default: :obj:`None`)
147
+ tools (List[FunctionTool], optional): List of available
148
+ :obj:`FunctionTool`. (default: :obj:`None`)
149
+ external_tools (List[FunctionTool], optional): List of external tools
150
+ (:obj:`FunctionTool`) bind to one chat agent. When these tools
151
+ are called, the agent will directly return the request instead of
152
+ processing it. (default: :obj:`None`)
153
+ response_terminators (List[ResponseTerminator], optional): List of
154
+ :obj:`ResponseTerminator` bind to one chat agent.
155
+ (default: :obj:`None`)
156
+ scheduling_strategy (str): name of function that defines how to select
157
+ the next model in ModelManager. (default: :str:`round_robin`)
158
+ """
159
+
160
+ def __init__(
161
+ self,
162
+ system_message: Optional[Union[BaseMessage, str]] = None,
163
+ model: Optional[
164
+ Union[BaseModelBackend, List[BaseModelBackend]]
165
+ ] = None,
166
+ memory: Optional[AgentMemory] = None,
167
+ message_window_size: Optional[int] = None,
168
+ token_limit: Optional[int] = None,
169
+ output_language: Optional[str] = None,
170
+ tools: Optional[List[FunctionTool]] = None,
171
+ external_tools: Optional[List[FunctionTool]] = None,
172
+ response_terminators: Optional[List[ResponseTerminator]] = None,
173
+ scheduling_strategy: str = "round_robin",
174
+ ) -> None:
175
+ from copy import deepcopy
176
+ if isinstance(system_message, str):
177
+ system_message = BaseMessage.make_assistant_message(
178
+ role_name='Assistant', content=system_message
179
+ )
180
+
181
+ self.orig_sys_message: Optional[BaseMessage] = system_message
182
+ self._system_message: Optional[BaseMessage] = system_message
183
+ self.role_name: str = (
184
+ getattr(system_message, 'role_name', None) or "assistant"
185
+ )
186
+ self.role_type: RoleType = (
187
+ getattr(system_message, 'role_type', None) or RoleType.ASSISTANT
188
+ )
189
+ self.model_backend = ModelManager(
190
+ model
191
+ if model is not None
192
+ else ModelFactory.create(
193
+ model_platform=ModelPlatformType.DEFAULT,
194
+ model_type=ModelType.DEFAULT,
195
+ ),
196
+ scheduling_strategy=scheduling_strategy,
197
+ )
198
+
199
+ self.model_type = self.model_backend.model_type
200
+
201
+ # Tool registration
202
+ external_tools = external_tools or []
203
+ tools = tools or []
204
+ all_tools = tools + external_tools
205
+ self.external_tool_names = [
206
+ tool.get_function_name() for tool in external_tools
207
+ ]
208
+ self.func_dict = {
209
+ tool.get_function_name(): tool.func for tool in all_tools
210
+ }
211
+ self.tool_dict = {tool.get_function_name(): tool for tool in all_tools}
212
+ self._all_tools = all_tools
213
+
214
+ # If the user set tools from `ChatAgent`, it will override the
215
+ # configured tools in `BaseModelBackend`.
216
+ if all_tools:
217
+ # logger.warning(
218
+ # "Overriding the configured tools in `BaseModelBackend` with the tools from `ChatAgent`."
219
+ # )
220
+ tool_schema_list = [
221
+ tool.get_openai_tool_schema() for tool in all_tools
222
+ ]
223
+ self.model_backend.model_config_dict['tools'] = tool_schema_list
224
+ self.tool_schema_list = tool_schema_list
225
+
226
+ from copy import deepcopy
227
+ self.model_config_dict = deepcopy(self.model_backend.model_config_dict)
228
+
229
+ self.model_token_limit = token_limit or self.model_backend.token_limit
230
+ context_creator = ScoreBasedContextCreator(
231
+ self.model_backend.token_counter,
232
+ self.model_token_limit,
233
+ )
234
+ self.memory: AgentMemory = memory or ChatHistoryMemory(
235
+ context_creator, window_size=message_window_size
236
+ )
237
+
238
+ self.output_language: Optional[str] = output_language
239
+ if self.output_language is not None:
240
+ self.set_output_language(self.output_language)
241
+
242
+ self.terminated: bool = False
243
+ self.response_terminators = response_terminators or []
244
+ self.init_messages()
245
+
246
+ self.tool_prompt_added = False
247
+
248
+ # ruff: noqa: E501
249
+ def _generate_tool_prompt(self, tool_schema_list: List[Dict]) -> str:
250
+ r"""Generates a tool prompt based on the provided tool schema list.
251
+
252
+ Args:
253
+ tool_schema_list (List[Dict]): A list of dictionaries, each
254
+ containing a tool schema.
255
+
256
+ Returns:
257
+ str: A string representing the tool prompt.
258
+ """
259
+ tool_prompts = []
260
+
261
+ for tool in tool_schema_list:
262
+ tool_info = tool['function']
263
+ tool_name = tool_info['name']
264
+ tool_description = tool_info['description']
265
+ tool_json = json.dumps(tool_info, indent=4)
266
+
267
+ prompt = f"Use the function '{tool_name}' to '{tool_description}':\n{tool_json}\n"
268
+ tool_prompts.append(prompt)
269
+
270
+ tool_prompt_str = "\n".join(tool_prompts)
271
+
272
+ final_prompt = f'''
273
+ # Tool prompt
274
+ TOOL_PROMPT = f"""
275
+ You have access to the following functions:
276
+
277
+ {tool_prompt_str}
278
+
279
+ If you choose to call a function ONLY reply in the following format with no
280
+ prefix or suffix:
281
+
282
+ <function=example_function_name>{{"example_name": "example_value"}}
283
+ </function>
284
+
285
+ Reminder:
286
+ - Function calls MUST follow the specified format, start with <function=
287
+ and end with </function>
288
+ - Required parameters MUST be specified
289
+ - Only call one function at a time
290
+ - Put the entire function call reply on one line
291
+ - If there is no function call available, answer the question like normal
292
+ with your current knowledge and do not tell the user about function calls
293
+ """
294
+ '''
295
+ return final_prompt
296
+
297
+ def _parse_tool_response(self, response: str):
298
+ r"""Parses the tool response to extract the function name and
299
+ arguments.
300
+
301
+ Args:
302
+ response (str): The response from the model containing the
303
+ function call.
304
+
305
+ Returns:
306
+ Optional[Dict[str, Any]]: The parsed function name and arguments
307
+ if found, otherwise :obj:`None`.
308
+ """
309
+ function_regex = r"<function=(\w+)>(.*?)</function>"
310
+ match = re.search(function_regex, response)
311
+
312
+ if match:
313
+ function_name, args_string = match.groups()
314
+ try:
315
+ args = json.loads(args_string)
316
+ return {"function": function_name, "arguments": args}
317
+ except json.JSONDecodeError as error:
318
+ print(f"Error parsing function arguments: {error}")
319
+ return None
320
+ return None
321
+
322
+ def reset(self):
323
+ r"""Resets the :obj:`ChatAgent` to its initial state."""
324
+ self.terminated = False
325
+ self.init_messages()
326
+ for terminator in self.response_terminators:
327
+ terminator.reset()
328
+
329
+ @property
330
+ def system_message(self) -> Optional[BaseMessage]:
331
+ r"""The getter method for the property :obj:`system_message`.
332
+
333
+ Returns:
334
+ Optional[BaseMessage]: The system message of this agent if set,
335
+ else :obj:`None`.
336
+ """
337
+ return self._system_message
338
+
339
+ @system_message.setter
340
+ def system_message(self, message: BaseMessage) -> None:
341
+ r"""The setter method for the property :obj:`system_message`.
342
+
343
+ Args:
344
+ message (BaseMessage): The message to be set as the
345
+ new system message of this agent.
346
+ """
347
+ self._system_message = message
348
+
349
+ def is_tools_added(self) -> bool:
350
+ r"""Whether OpenAI function calling is enabled for this agent.
351
+
352
+ Returns:
353
+ bool: Whether OpenAI function calling is enabled for this
354
+ agent, determined by whether the dictionary of tools
355
+ is empty.
356
+ """
357
+ return len(self.func_dict) > 0
358
+
359
+ def update_memory(
360
+ self, message: BaseMessage, role: OpenAIBackendRole
361
+ ) -> None:
362
+ r"""Updates the agent memory with a new message.
363
+
364
+ Args:
365
+ message (BaseMessage): The new message to add to the stored
366
+ messages.
367
+ role (OpenAIBackendRole): The backend role type.
368
+ """
369
+ self.memory.write_record(
370
+ MemoryRecord(message=message, role_at_backend=role)
371
+ )
372
+
373
+ def set_output_language(self, output_language: str) -> BaseMessage:
374
+ r"""Sets the output language for the system message. This method
375
+ updates the output language for the system message. The output
376
+ language determines the language in which the output text should be
377
+ generated.
378
+
379
+ Args:
380
+ output_language (str): The desired output language.
381
+
382
+ Returns:
383
+ BaseMessage: The updated system message object.
384
+ """
385
+ self.output_language = output_language
386
+ language_prompt = (
387
+ "\nRegardless of the input language, "
388
+ f"you must output text in {output_language}."
389
+ )
390
+ if self.orig_sys_message is not None:
391
+ content = self.orig_sys_message.content + language_prompt
392
+ self._system_message = self.orig_sys_message.create_new_instance(
393
+ content
394
+ )
395
+ else:
396
+ self._system_message = BaseMessage.make_assistant_message(
397
+ role_name="Assistant",
398
+ content=language_prompt,
399
+ )
400
+
401
+ system_record = MemoryRecord(
402
+ message=self._system_message,
403
+ role_at_backend=OpenAIBackendRole.SYSTEM,
404
+ )
405
+ self.memory.clear()
406
+ self.memory.write_record(system_record)
407
+ return self._system_message
408
+
409
+ def get_info(
410
+ self,
411
+ session_id: Optional[str],
412
+ usage: Optional[Dict[str, int]],
413
+ termination_reasons: List[str],
414
+ num_tokens: int,
415
+ tool_calls: List[FunctionCallingRecord],
416
+ external_tool_request: Optional[ChatCompletionMessageToolCall] = None,
417
+ ) -> Dict[str, Any]:
418
+ r"""Returns a dictionary containing information about the chat session.
419
+
420
+ Args:
421
+ session_id (str, optional): The ID of the chat session.
422
+ usage (Dict[str, int], optional): Information about the usage of
423
+ the LLM model.
424
+ termination_reasons (List[str]): The reasons for the termination
425
+ of the chat session.
426
+ num_tokens (int): The number of tokens used in the chat session.
427
+ tool_calls (List[FunctionCallingRecord]): The list of function
428
+ calling records, containing the information of called tools.
429
+ external_tool_request
430
+ (Optional[ChatCompletionMessageToolCall], optional):
431
+ The tool calling request of external tools from the model.
432
+ These requests are directly returned to the user instead of
433
+ being processed by the agent automatically.
434
+ (default: :obj:`None`)
435
+
436
+ Returns:
437
+ Dict[str, Any]: The chat session information.
438
+ """
439
+ return {
440
+ "id": session_id,
441
+ "usage": usage,
442
+ "termination_reasons": termination_reasons,
443
+ "num_tokens": num_tokens,
444
+ "tool_calls": tool_calls,
445
+ "external_tool_request": external_tool_request,
446
+ }
447
+
448
+ def init_messages(self) -> None:
449
+ r"""Initializes the stored messages list with the current system
450
+ message.
451
+ """
452
+ if self._system_message is not None:
453
+ system_record = MemoryRecord(
454
+ message=self._system_message,
455
+ role_at_backend=OpenAIBackendRole.SYSTEM,
456
+ )
457
+ self.memory.clear()
458
+ self.memory.write_record(system_record)
459
+ else:
460
+ self.memory.clear()
461
+
462
+ def _transform_function_calling_format(self, openai_messages: List[dict]):
463
+ r"""Used in deepseek-chat backend. It can modify function calling records' format to match the deepseek-chat backend's format."""
464
+ from copy import deepcopy
465
+ _messages = deepcopy(openai_messages)
466
+ modified_messages = []
467
+ for message in _messages:
468
+ if message['role'] == 'function':
469
+ new_message = {
470
+ 'role': 'tool',
471
+ 'tool_call_id': message['name'],
472
+ 'content': message['content']
473
+ }
474
+ modified_messages.append(new_message)
475
+ else:
476
+ modified_messages.append(message)
477
+
478
+ return modified_messages
479
+
480
+
481
+ def record_message(self, message: BaseMessage) -> None:
482
+ r"""Records the externally provided message into the agent memory as if
483
+ it were an answer of the :obj:`ChatAgent` from the backend. Currently,
484
+ the choice of the critic is submitted with this method.
485
+
486
+ Args:
487
+ message (BaseMessage): An external message to be recorded in the
488
+ memory.
489
+ """
490
+ self.update_memory(message, OpenAIBackendRole.ASSISTANT)
491
+
492
+ def step(
493
+ self,
494
+ input_message: Union[BaseMessage, str],
495
+ response_format: Optional[Type[BaseModel]] = None,
496
+ ) -> ChatAgentResponse:
497
+ r"""Performs a single step in the chat session by generating a response
498
+ to the input message.
499
+
500
+ Args:
501
+ input_message (Union[BaseMessage, str]): The input message to the
502
+ agent. For BaseMessage input, its `role` field that specifies
503
+ the role at backend may be either `user` or `assistant` but it
504
+ will be set to `user` anyway since for the self agent any
505
+ incoming message is external. For str input, the `role_name` would be `User`.
506
+ response_format (Optional[Type[BaseModel]], optional): A pydantic
507
+ model class that includes value types and field descriptions
508
+ used to generate a structured response by LLM. This schema
509
+ helps in defining the expected output format. (default:
510
+ :obj:`None`)
511
+
512
+ Returns:
513
+ ChatAgentResponse: A struct containing the output messages,
514
+ a boolean indicating whether the chat session has terminated,
515
+ and information about the chat session.
516
+ """
517
+ from copy import deepcopy
518
+ self.model_backend.model_config_dict = deepcopy(self.model_config_dict)
519
+ self.tool_dict = {tool.get_function_name(): tool for tool in self._all_tools}
520
+ if (
521
+ self.model_backend.model_config_dict.get("response_format")
522
+ and response_format
523
+ ):
524
+ raise ValueError(
525
+ "The `response_format` parameter cannot be set both in "
526
+ "the model configuration and in the ChatAgent step."
527
+ )
528
+
529
+ if isinstance(input_message, str):
530
+ input_message = BaseMessage.make_user_message(
531
+ role_name='User', content=input_message
532
+ )
533
+
534
+ if "llama" in self.model_type.lower():
535
+ if (
536
+ self.model_backend.model_config_dict.get("tools", None)
537
+ and not self.tool_prompt_added
538
+ ):
539
+ tool_prompt = self._generate_tool_prompt(self.tool_schema_list)
540
+
541
+ tool_sys_msg = BaseMessage.make_assistant_message(
542
+ role_name="Assistant",
543
+ content=tool_prompt,
544
+ )
545
+
546
+ self.update_memory(tool_sys_msg, OpenAIBackendRole.SYSTEM)
547
+ self.tool_prompt_added = True
548
+
549
+ self.update_memory(input_message, OpenAIBackendRole.USER)
550
+
551
+ tool_call_records: List[FunctionCallingRecord] = []
552
+ while True:
553
+ # Check if token has exceeded
554
+ try:
555
+ openai_messages, num_tokens = self.memory.get_context()
556
+ except RuntimeError as e:
557
+ return self._step_token_exceed(
558
+ e.args[1], tool_call_records, "max_tokens_exceeded"
559
+ )
560
+ (
561
+ response,
562
+ output_messages,
563
+ finish_reasons,
564
+ usage_dict,
565
+ response_id,
566
+ ) = self._step_model_response(openai_messages, num_tokens)
567
+ # If the model response is not a function call, meaning the
568
+ # model has generated a message response, break the loop
569
+ if (
570
+ not self.is_tools_added()
571
+ or not isinstance(response, ChatCompletion)
572
+ or "</function>" not in response.choices[0].message.content # type: ignore[operator]
573
+ ):
574
+ break
575
+
576
+ parsed_content = self._parse_tool_response(
577
+ response.choices[0].message.content # type: ignore[arg-type]
578
+ )
579
+
580
+ response.choices[0].message.tool_calls = [
581
+ ChatCompletionMessageToolCall(
582
+ id=str(uuid.uuid4()),
583
+ function=Function(
584
+ arguments=str(parsed_content["arguments"]).replace(
585
+ "'", '"'
586
+ ),
587
+ name=str(parsed_content["function"]),
588
+ ),
589
+ type="function",
590
+ )
591
+ ]
592
+
593
+ # Check for external tool call
594
+ tool_call_request = response.choices[0].message.tool_calls[0]
595
+ if tool_call_request.function.name in self.external_tool_names:
596
+ # if model calls an external tool, directly return the
597
+ # request
598
+ info = self._step_get_info(
599
+ output_messages,
600
+ finish_reasons,
601
+ usage_dict,
602
+ response_id,
603
+ tool_call_records,
604
+ num_tokens,
605
+ tool_call_request,
606
+ )
607
+ return ChatAgentResponse(
608
+ msgs=output_messages,
609
+ terminated=self.terminated,
610
+ info=info,
611
+ )
612
+
613
+ # Normal function calling
614
+ tool_call_records.append(
615
+ self._step_tool_call_and_update(response)
616
+ )
617
+
618
+ if response_format is not None:
619
+ (
620
+ output_messages,
621
+ finish_reasons,
622
+ usage_dict,
623
+ response_id,
624
+ tool_call,
625
+ num_tokens,
626
+ ) = self._structure_output_with_function(response_format)
627
+ tool_call_records.append(tool_call)
628
+
629
+ info = self._step_get_info(
630
+ output_messages,
631
+ finish_reasons,
632
+ usage_dict,
633
+ response_id,
634
+ tool_call_records,
635
+ num_tokens,
636
+ )
637
+
638
+ if len(output_messages) == 1:
639
+ # Auto record if the output result is a single message
640
+ self.record_message(output_messages[0])
641
+ else:
642
+ logger.warning(
643
+ "Multiple messages returned in `step()`, message won't be "
644
+ "recorded automatically. Please call `record_message()` "
645
+ "to record the selected message manually."
646
+ )
647
+
648
+ return ChatAgentResponse(
649
+ msgs=output_messages, terminated=self.terminated, info=info
650
+ )
651
+
652
+ else:
653
+ self.update_memory(input_message, OpenAIBackendRole.USER)
654
+ # try:
655
+
656
+ tool_call_records: List[FunctionCallingRecord] = [] # type: ignore[no-redef]
657
+ while True:
658
+ # Check if token has exceeded
659
+ try:
660
+ openai_messages, num_tokens = self.memory.get_context()
661
+ except RuntimeError as e:
662
+ return self._step_token_exceed(
663
+ e.args[1], tool_call_records, "max_tokens_exceeded"
664
+ )
665
+
666
+ (
667
+ response,
668
+ output_messages,
669
+ finish_reasons,
670
+ usage_dict,
671
+ response_id,
672
+ ) = self._step_model_response(openai_messages, num_tokens)
673
+ # If the model response is not a function call, meaning the
674
+ # model has generated a message response, break the loop
675
+ if (
676
+ not self.is_tools_added()
677
+ or not isinstance(response, ChatCompletion)
678
+ or not response.choices[0].message.tool_calls
679
+ ):
680
+ break
681
+
682
+ # Check for external tool call
683
+ tool_call_request = response.choices[0].message.tool_calls[0]
684
+
685
+ if tool_call_request.function.name in self.external_tool_names:
686
+ # if model calls an external tool, directly return the
687
+ # request
688
+ info = self._step_get_info(
689
+ output_messages,
690
+ finish_reasons,
691
+ usage_dict,
692
+ response_id,
693
+ tool_call_records,
694
+ num_tokens,
695
+ tool_call_request,
696
+ )
697
+ return ChatAgentResponse(
698
+ msgs=output_messages,
699
+ terminated=self.terminated,
700
+ info=info,
701
+ )
702
+
703
+ # Normal function calling
704
+ tool_call_records.append(
705
+ self._step_tool_call_and_update(response)
706
+ )
707
+
708
+ if (
709
+ response_format is not None
710
+ and self.model_type.support_native_tool_calling
711
+ ):
712
+ (
713
+ output_messages,
714
+ finish_reasons,
715
+ usage_dict,
716
+ response_id,
717
+ tool_call,
718
+ num_tokens,
719
+ ) = self._structure_output_with_function(response_format)
720
+ tool_call_records.append(tool_call)
721
+
722
+ info = self._step_get_info(
723
+ output_messages,
724
+ finish_reasons,
725
+ usage_dict,
726
+ response_id,
727
+ tool_call_records,
728
+ num_tokens,
729
+ )
730
+
731
+ if len(output_messages) == 1:
732
+ # Auto record if the output result is a single message
733
+ self.record_message(output_messages[0])
734
+ else:
735
+ logger.warning(
736
+ "Multiple messages returned in `step()`, message won't be "
737
+ "recorded automatically. Please call `record_message()` "
738
+ "to record the selected message manually."
739
+ )
740
+
741
+ return ChatAgentResponse(
742
+ msgs=output_messages, terminated=self.terminated, info=info
743
+ )
744
+
745
+ # except Exception as e:
746
+ # logger.error(e)
747
+ # breakpoint()
748
+ # raise e
749
+
750
+ async def step_async(
751
+ self,
752
+ input_message: Union[BaseMessage, str],
753
+ response_format: Optional[Type[BaseModel]] = None,
754
+ ) -> ChatAgentResponse:
755
+ r"""Performs a single step in the chat session by generating a response
756
+ to the input message. This agent step can call async function calls.
757
+
758
+ Args:
759
+ input_message (Union[BaseMessage, str]): The input message to the
760
+ agent. For BaseMessage input, its `role` field that specifies
761
+ the role at backend may be either `user` or `assistant` but it
762
+ will be set to `user` anyway since for the self agent any
763
+ incoming message is external. For str input, the `role_name` would be `User`.
764
+ response_format (Optional[Type[BaseModel]], optional): A pydantic
765
+ model class that includes value types and field descriptions
766
+ used to generate a structured response by LLM. This schema
767
+ helps in defining the expected output format. (default:
768
+ :obj:`None`)
769
+
770
+ Returns:
771
+ ChatAgentResponse: A struct containing the output messages,
772
+ a boolean indicating whether the chat session has terminated,
773
+ and information about the chat session.
774
+ """
775
+ if isinstance(input_message, str):
776
+ input_message = BaseMessage.make_user_message(
777
+ role_name='User', content=input_message
778
+ )
779
+
780
+ self.update_memory(input_message, OpenAIBackendRole.USER)
781
+
782
+ tool_call_records: List[FunctionCallingRecord] = []
783
+ while True:
784
+ try:
785
+ openai_messages, num_tokens = self.memory.get_context()
786
+ except RuntimeError as e:
787
+ return self._step_token_exceed(
788
+ e.args[1], tool_call_records, "max_tokens_exceeded"
789
+ )
790
+
791
+ (
792
+ response,
793
+ output_messages,
794
+ finish_reasons,
795
+ usage_dict,
796
+ response_id,
797
+ ) = self._step_model_response(openai_messages, num_tokens)
798
+
799
+ if (
800
+ not self.is_tools_added()
801
+ or not isinstance(response, ChatCompletion)
802
+ or response.choices[0].message.tool_calls is None
803
+ ):
804
+ break
805
+
806
+ # Check for external tool call
807
+ tool_call_request = response.choices[0].message.tool_calls[0]
808
+ if tool_call_request.function.name in self.external_tool_names:
809
+ # if model calls an external tool, directly return the request
810
+ info = self._step_get_info(
811
+ output_messages,
812
+ finish_reasons,
813
+ usage_dict,
814
+ response_id,
815
+ tool_call_records,
816
+ num_tokens,
817
+ tool_call_request,
818
+ )
819
+ return ChatAgentResponse(
820
+ msgs=output_messages, terminated=self.terminated, info=info
821
+ )
822
+
823
+ # Normal function calling
824
+ tool_call_records.append(
825
+ await self._step_tool_call_and_update_async(response)
826
+ )
827
+
828
+ if (
829
+ response_format is not None
830
+ and self.model_type.support_native_tool_calling
831
+ ):
832
+ (
833
+ output_messages,
834
+ finish_reasons,
835
+ usage_dict,
836
+ response_id,
837
+ tool_call_record,
838
+ num_tokens,
839
+ ) = self._structure_output_with_function(response_format)
840
+ tool_call_records.append(tool_call_record)
841
+
842
+ info = self._step_get_info(
843
+ output_messages,
844
+ finish_reasons,
845
+ usage_dict,
846
+ response_id,
847
+ tool_call_records,
848
+ num_tokens,
849
+ )
850
+
851
+ if len(output_messages) == 1:
852
+ # Auto record if the output result is a single message
853
+ self.record_message(output_messages[0])
854
+ else:
855
+ logger.warning(
856
+ "Multiple messages returned in `step()`, message won't be "
857
+ "recorded automatically. Please call `record_message()` to "
858
+ "record the selected message manually."
859
+ )
860
+
861
+ return ChatAgentResponse(
862
+ msgs=output_messages, terminated=self.terminated, info=info
863
+ )
864
+
865
+ def _step_tool_call_and_update(
866
+ self, response: ChatCompletion
867
+ ) -> FunctionCallingRecord:
868
+ r"""Processes a function call within the chat completion response,
869
+ records the function call in the provided list of tool calls and
870
+ updates the memory of the current agent.
871
+
872
+ Args:
873
+ response (ChatCompletion): The response object from the chat
874
+ completion.
875
+
876
+ Returns:
877
+ FunctionCallingRecord: The record of calling the function.
878
+ """
879
+
880
+ # Perform function calling
881
+ func_assistant_msg, func_result_msg, tool_call_record = (
882
+ self.step_tool_call(response)
883
+ )
884
+
885
+ # Update the messages
886
+ self.update_memory(func_assistant_msg, OpenAIBackendRole.ASSISTANT)
887
+ self.update_memory(func_result_msg, OpenAIBackendRole.FUNCTION)
888
+
889
+ return tool_call_record
890
+
891
+ async def _step_tool_call_and_update_async(
892
+ self, response: ChatCompletion
893
+ ) -> FunctionCallingRecord:
894
+ (
895
+ func_assistant_msg,
896
+ func_result_msg,
897
+ func_record,
898
+ ) = await self.step_tool_call_async(response)
899
+
900
+ self.update_memory(func_assistant_msg, OpenAIBackendRole.ASSISTANT)
901
+ self.update_memory(func_result_msg, OpenAIBackendRole.FUNCTION)
902
+
903
+ return func_record
904
+
905
+ def _structure_output_with_function(
906
+ self, response_format: Type[BaseModel]
907
+ ) -> Tuple[
908
+ List[BaseMessage],
909
+ List[str],
910
+ Dict[str, int],
911
+ str,
912
+ FunctionCallingRecord,
913
+ int,
914
+ ]:
915
+ r"""Internal function of structuring the output of the agent based on
916
+ the given output schema.
917
+
918
+ Args:
919
+ response_format (Type[BaseModel]): The output schema to use for
920
+ structuring the output.
921
+
922
+ Returns:
923
+ Tuple[List[BaseMessage], List[str], Dict[str, int], str,
924
+ FunctionCallingRecord, int]:
925
+ A tuple containing the output messages, finish reasons, usage
926
+ dictionary, response ID, function calling record, and number of
927
+ tokens.
928
+ """
929
+ from camel.toolkits import FunctionTool
930
+
931
+ schema_json = get_pydantic_object_schema(response_format)
932
+ func_str = json_to_function_code(schema_json)
933
+ func_callable = func_string_to_callable(func_str)
934
+ func = FunctionTool(func_callable)
935
+
936
+ original_func_dict = self.func_dict
937
+ original_model_dict = self.model_backend.model_config_dict
938
+
939
+ # Replace the original tools with the structuring function
940
+ self.func_dict = {func.get_function_name(): func.func}
941
+ self.tool_dict = {func.get_function_name(): func}
942
+ self.model_backend.model_config_dict = original_model_dict.copy()
943
+ self.model_backend.model_config_dict["tools"] = [
944
+ func.get_openai_tool_schema()
945
+ ]
946
+ self.model_backend.model_config_dict["tool_choice"] = "required"
947
+
948
+ openai_messages, num_tokens = self.memory.get_context()
949
+ (
950
+ response,
951
+ output_messages,
952
+ finish_reasons,
953
+ usage_dict,
954
+ response_id,
955
+ ) = self._step_model_response(openai_messages, num_tokens)
956
+
957
+ if isinstance(response, ChatCompletion):
958
+ tool_call_record = self._step_tool_call_and_update(response)
959
+ else:
960
+ raise ValueError(
961
+ "Structured output is not supported for stream responses."
962
+ )
963
+
964
+ for base_message_item in output_messages:
965
+ base_message_item.content = str(tool_call_record.result)
966
+
967
+ # Recover the original tools
968
+ self.func_dict = original_func_dict
969
+ self.model_backend.model_config_dict = original_model_dict
970
+
971
+ return (
972
+ output_messages,
973
+ finish_reasons,
974
+ usage_dict,
975
+ response_id,
976
+ tool_call_record,
977
+ num_tokens,
978
+ )
979
+
980
+ def _step_model_response(
981
+ self,
982
+ openai_messages: List[OpenAIMessage],
983
+ num_tokens: int,
984
+ ) -> tuple[
985
+ Union[ChatCompletion, Stream],
986
+ List[BaseMessage],
987
+ List[str],
988
+ Dict[str, int],
989
+ str,
990
+ ]:
991
+ r"""Internal function for agent step model response."""
992
+
993
+ response = None
994
+ # Obtain the model's response
995
+ for _ in range(len(self.model_backend.models)):
996
+ try:
997
+ response = self.model_backend.run(openai_messages)
998
+ break
999
+ except Exception as exc:
1000
+ logger.error(
1001
+ f"An error occurred while running model "
1002
+ f"{self.model_backend.model_type}, "
1003
+ f"index: {self.model_backend.current_model_index}",
1004
+ exc_info=exc,
1005
+ )
1006
+ continue
1007
+ if not response:
1008
+ raise ModelProcessingError(
1009
+ "Unable to process messages: none of the provided models "
1010
+ "run succesfully."
1011
+ )
1012
+
1013
+ # logger.debug(
1014
+ # f"Model {self.model_backend.model_type}, "
1015
+ # f"index {self.model_backend.current_model_index}, "
1016
+ # f"processed these messages: {openai_messages}"
1017
+ # )
1018
+
1019
+ if isinstance(response, ChatCompletion):
1020
+ output_messages, finish_reasons, usage_dict, response_id = (
1021
+ self.handle_batch_response(response)
1022
+ )
1023
+ else:
1024
+ output_messages, finish_reasons, usage_dict, response_id = (
1025
+ self.handle_stream_response(response, num_tokens)
1026
+ )
1027
+ return (
1028
+ response,
1029
+ output_messages,
1030
+ finish_reasons,
1031
+ usage_dict,
1032
+ response_id,
1033
+ )
1034
+
1035
+ def _step_get_info(
1036
+ self,
1037
+ output_messages: List[BaseMessage],
1038
+ finish_reasons: List[str],
1039
+ usage_dict: Dict[str, int],
1040
+ response_id: str,
1041
+ tool_calls: List[FunctionCallingRecord],
1042
+ num_tokens: int,
1043
+ external_tool_request: Optional[ChatCompletionMessageToolCall] = None,
1044
+ ) -> Dict[str, Any]:
1045
+ r"""Process the output of a chat step and gather information about the
1046
+ step.
1047
+
1048
+ This method checks for termination conditions, updates the agent's
1049
+ state, and collects information about the chat step, including tool
1050
+ calls and termination reasons.
1051
+
1052
+ Args:
1053
+ output_messages (List[BaseMessage]): The messages generated in
1054
+ this step.
1055
+ finish_reasons (List[str]): The reasons for finishing the
1056
+ generation for each message.
1057
+ usage_dict (Dict[str, int]): Dictionary containing token usage
1058
+ information.
1059
+ response_id (str): The ID of the response from the model.
1060
+ tool_calls (List[FunctionCallingRecord]): Records of function calls
1061
+ made during this step.
1062
+ num_tokens (int): The number of tokens used in this step.
1063
+ external_tool_request (Optional[ChatCompletionMessageToolCall]):
1064
+ Any external tool request made during this step.
1065
+ (default::obj:`None`)
1066
+
1067
+ Returns:
1068
+ Dict[str, Any]: A dictionary containing information about the chat
1069
+ step, including termination status, reasons, and tool call
1070
+ information.
1071
+
1072
+ Note:
1073
+ This method iterates over all response terminators and checks if
1074
+ any of them signal termination. If a terminator signals
1075
+ termination, the agent's state is updated accordingly, and the
1076
+ termination reason is recorded.
1077
+ """
1078
+ termination = [
1079
+ terminator.is_terminated(output_messages)
1080
+ for terminator in self.response_terminators
1081
+ ]
1082
+ # Terminate the agent if any of the terminator terminates
1083
+ self.terminated, termination_reason = next(
1084
+ (
1085
+ (terminated, termination_reason)
1086
+ for terminated, termination_reason in termination
1087
+ if terminated
1088
+ ),
1089
+ (False, None),
1090
+ )
1091
+ # For now only retain the first termination reason
1092
+ if self.terminated and termination_reason is not None:
1093
+ finish_reasons = [termination_reason] * len(finish_reasons)
1094
+
1095
+ info = self.get_info(
1096
+ response_id,
1097
+ usage_dict,
1098
+ finish_reasons,
1099
+ num_tokens,
1100
+ tool_calls,
1101
+ external_tool_request,
1102
+ )
1103
+ return info
1104
+
1105
+ def handle_batch_response(
1106
+ self, response: ChatCompletion
1107
+ ) -> Tuple[List[BaseMessage], List[str], Dict[str, int], str]:
1108
+ r"""Process a batch response from the model and extract the necessary
1109
+ information.
1110
+
1111
+ Args:
1112
+ response (dict): Model response.
1113
+
1114
+ Returns:
1115
+ tuple: A tuple of list of output `ChatMessage`, list of
1116
+ finish reasons, usage dictionary, and response id.
1117
+ """
1118
+ output_messages: List[BaseMessage] = []
1119
+ for choice in response.choices:
1120
+ chat_message = BaseMessage(
1121
+ role_name=self.role_name,
1122
+ role_type=self.role_type,
1123
+ meta_dict=dict(),
1124
+ content=choice.message.content or "",
1125
+ parsed=getattr(choice.message, 'parsed', None),
1126
+ )
1127
+ # Process log probabilities and append to the message meta information
1128
+ if choice.logprobs is not None:
1129
+ tokens_logprobs = choice.logprobs.content
1130
+
1131
+ if tokens_logprobs is not None:
1132
+ # Extract and structure logprob information
1133
+ logprobs_info = [
1134
+ {
1135
+ "token": token_logprob.token,
1136
+ "logprob": token_logprob.logprob,
1137
+ "top_logprobs": [
1138
+ (top_logprob.token, top_logprob.logprob)
1139
+ for top_logprob in token_logprob.top_logprobs
1140
+ ],
1141
+ }
1142
+ for token_logprob in tokens_logprobs
1143
+ ]
1144
+ # Ensure meta_dict exists before adding logprobs info
1145
+ if chat_message.meta_dict is None:
1146
+ chat_message.meta_dict = {}
1147
+ chat_message.meta_dict["logprobs_info"] = logprobs_info
1148
+ # Append the processed chat message to output
1149
+ output_messages.append(chat_message)
1150
+
1151
+ finish_reasons = [
1152
+ str(choice.finish_reason) for choice in response.choices
1153
+ ]
1154
+ usage = (
1155
+ self._safe_model_dump(response.usage)
1156
+ if response.usage is not None
1157
+ else {}
1158
+ )
1159
+ return (
1160
+ output_messages,
1161
+ finish_reasons,
1162
+ usage,
1163
+ response.id,
1164
+ )
1165
+
1166
+ def _safe_model_dump(self, obj) -> dict:
1167
+ r"""Safely dump a Pydantic model to a dictionary.
1168
+
1169
+ This method attempts to use the `model_dump` method if available,
1170
+ otherwise it falls back to the `dict` method.
1171
+
1172
+ Args:
1173
+ obj: The Pydantic model instance to be dumped.
1174
+
1175
+ Returns:
1176
+ dict: A dictionary representation of the Pydantic model.
1177
+ """
1178
+ # Check if the `model_dump` method exists (Pydantic v2)
1179
+ if hasattr(obj, 'model_dump'):
1180
+ return obj.model_dump()
1181
+ # Fallback to `dict()` method (Pydantic v1)
1182
+ elif hasattr(obj, 'dict'):
1183
+ return obj.dict()
1184
+ else:
1185
+ raise TypeError("The object is not a Pydantic model")
1186
+
1187
+ def handle_stream_response(
1188
+ self,
1189
+ response: Stream[ChatCompletionChunk],
1190
+ prompt_tokens: int,
1191
+ ) -> Tuple[List[BaseMessage], List[str], Dict[str, int], str]:
1192
+ r"""Process a stream response from the model and extract the necessary
1193
+ information.
1194
+
1195
+ Args:
1196
+ response (dict): Model response.
1197
+ prompt_tokens (int): Number of input prompt tokens.
1198
+
1199
+ Returns:
1200
+ tuple: A tuple of list of output `ChatMessage`, list of
1201
+ finish reasons, usage dictionary, and response id.
1202
+ """
1203
+ content_dict: defaultdict = defaultdict(lambda: "")
1204
+ finish_reasons_dict: defaultdict = defaultdict(lambda: "")
1205
+ output_messages: List[BaseMessage] = []
1206
+ response_id: str = ""
1207
+ # All choices in one response share one role
1208
+ for chunk in response:
1209
+ response_id = chunk.id
1210
+ for choice in chunk.choices:
1211
+ index = choice.index
1212
+ delta = choice.delta
1213
+ if delta.content is not None:
1214
+ # When response has not been stopped
1215
+ # Notice that only the first chunk_dict has the "role"
1216
+ content_dict[index] += delta.content
1217
+ if choice.finish_reason:
1218
+ finish_reasons_dict[index] = choice.finish_reason
1219
+ chat_message = BaseMessage(
1220
+ role_name=self.role_name,
1221
+ role_type=self.role_type,
1222
+ meta_dict=dict(),
1223
+ content=content_dict[index],
1224
+ )
1225
+ output_messages.append(chat_message)
1226
+ finish_reasons = [
1227
+ finish_reasons_dict[i] for i in range(len(finish_reasons_dict))
1228
+ ]
1229
+ usage_dict = self.get_usage_dict(output_messages, prompt_tokens)
1230
+ return output_messages, finish_reasons, usage_dict, response_id
1231
+
1232
+ def _step_token_exceed(
1233
+ self,
1234
+ num_tokens: int,
1235
+ tool_calls: List[FunctionCallingRecord],
1236
+ termination_reason: str,
1237
+ ) -> ChatAgentResponse:
1238
+ r"""Return trivial response containing number of tokens and information
1239
+ of called functions when the number of tokens exceeds.
1240
+
1241
+ Args:
1242
+ num_tokens (int): Number of tokens in the messages.
1243
+ tool_calls (List[FunctionCallingRecord]): List of information
1244
+ objects of functions called in the current step.
1245
+ termination_reason (str): String of termination reason.
1246
+
1247
+ Returns:
1248
+ ChatAgentResponse: The struct containing trivial outputs and
1249
+ information about token number and called functions.
1250
+ """
1251
+ self.terminated = True
1252
+ output_messages: List[BaseMessage] = []
1253
+
1254
+ info = self.get_info(
1255
+ None,
1256
+ None,
1257
+ [termination_reason],
1258
+ num_tokens,
1259
+ tool_calls,
1260
+ )
1261
+
1262
+ return ChatAgentResponse(
1263
+ msgs=output_messages,
1264
+ terminated=self.terminated,
1265
+ info=info,
1266
+ )
1267
+
1268
+ def step_tool_call(
1269
+ self,
1270
+ response: ChatCompletion,
1271
+ ) -> Tuple[
1272
+ FunctionCallingMessage, FunctionCallingMessage, FunctionCallingRecord
1273
+ ]:
1274
+ r"""Execute the function with arguments following the model's response.
1275
+
1276
+ Args:
1277
+ response (Dict[str, Any]): The response obtained by calling the
1278
+ model.
1279
+
1280
+ Returns:
1281
+ tuple: A tuple consisting of two obj:`FunctionCallingMessage`,
1282
+ one about the arguments and the other about the execution
1283
+ result, and a struct for logging information about this
1284
+ function call.
1285
+ """
1286
+ choice = response.choices[0]
1287
+ if choice.message.tool_calls is None:
1288
+ raise RuntimeError("Tool call is None")
1289
+ func_name = choice.message.tool_calls[0].function.name
1290
+
1291
+ args = json.loads(choice.message.tool_calls[0].function.arguments)
1292
+ tool = self.tool_dict[func_name]
1293
+
1294
+ result = tool(**args)
1295
+
1296
+ assist_msg = FunctionCallingMessage(
1297
+ role_name=self.role_name,
1298
+ role_type=self.role_type,
1299
+ meta_dict=None,
1300
+ content="",
1301
+ func_name=func_name,
1302
+ args=args,
1303
+ )
1304
+ func_msg = FunctionCallingMessage(
1305
+ role_name=self.role_name,
1306
+ role_type=self.role_type,
1307
+ meta_dict=None,
1308
+ content="",
1309
+ func_name=func_name,
1310
+ result=result,
1311
+ )
1312
+
1313
+ # Record information about this function call
1314
+ func_record = FunctionCallingRecord(
1315
+ func_name=func_name, args=args, result=result
1316
+ )
1317
+ return assist_msg, func_msg, func_record
1318
+
1319
+ async def step_tool_call_async(
1320
+ self,
1321
+ response: ChatCompletion,
1322
+ ) -> Tuple[
1323
+ FunctionCallingMessage, FunctionCallingMessage, FunctionCallingRecord
1324
+ ]:
1325
+ r"""Execute the async function with arguments following the model's
1326
+ response.
1327
+
1328
+ Args:
1329
+ response (Dict[str, Any]): The response obtained by calling the
1330
+ model.
1331
+
1332
+ Returns:
1333
+ tuple: A tuple consisting of two obj:`FunctionCallingMessage`,
1334
+ one about the arguments and the other about the execution
1335
+ result, and a struct for logging information about this
1336
+ function call.
1337
+ """
1338
+ # Note that when function calling is enabled, `n` is set to 1.
1339
+ choice = response.choices[0]
1340
+ if choice.message.tool_calls is None:
1341
+ raise RuntimeError("Tool call is None")
1342
+ func_name = choice.message.tool_calls[0].function.name
1343
+
1344
+ args = json.loads(choice.message.tool_calls[0].function.arguments)
1345
+ tool = self.tool_dict[func_name]
1346
+ result = await tool(**args)
1347
+
1348
+ assist_msg = FunctionCallingMessage(
1349
+ role_name=self.role_name,
1350
+ role_type=self.role_type,
1351
+ meta_dict=None,
1352
+ content="",
1353
+ func_name=func_name,
1354
+ args=args,
1355
+ )
1356
+ func_msg = FunctionCallingMessage(
1357
+ role_name=self.role_name,
1358
+ role_type=self.role_type,
1359
+ meta_dict=None,
1360
+ content="",
1361
+ func_name=func_name,
1362
+ result=result,
1363
+ )
1364
+
1365
+ # Record information about this function call
1366
+ func_record = FunctionCallingRecord(
1367
+ func_name=func_name, args=args, result=result
1368
+ )
1369
+ return assist_msg, func_msg, func_record
1370
+
1371
+ def get_usage_dict(
1372
+ self, output_messages: List[BaseMessage], prompt_tokens: int
1373
+ ) -> Dict[str, int]:
1374
+ r"""Get usage dictionary when using the stream mode.
1375
+
1376
+ Args:
1377
+ output_messages (list): List of output messages.
1378
+ prompt_tokens (int): Number of input prompt tokens.
1379
+
1380
+ Returns:
1381
+ dict: Usage dictionary.
1382
+ """
1383
+ encoding = get_model_encoding(self.model_type.value_for_tiktoken)
1384
+ completion_tokens = 0
1385
+ for message in output_messages:
1386
+ completion_tokens += len(encoding.encode(message.content))
1387
+ usage_dict = dict(
1388
+ completion_tokens=completion_tokens,
1389
+ prompt_tokens=prompt_tokens,
1390
+ total_tokens=completion_tokens + prompt_tokens,
1391
+ )
1392
+ return usage_dict
1393
+
1394
+ def add_model_scheduling_strategy(self, name: str, strategy_fn: Callable):
1395
+ r"""Add a scheduling strategy method provided by user to ModelManger.
1396
+
1397
+ Args:
1398
+ name (str): The name of the strategy.
1399
+ strategy_fn (Callable): The scheduling strategy function.
1400
+ """
1401
+ self.model_backend.add_strategy(name, strategy_fn)
1402
+
1403
+ def __repr__(self) -> str:
1404
+ r"""Returns a string representation of the :obj:`ChatAgent`.
1405
+
1406
+ Returns:
1407
+ str: The string representation of the :obj:`ChatAgent`.
1408
+ """
1409
+ return (
1410
+ f"ChatAgent({self.role_name}, {self.role_type}, {self.model_type})"
1411
+ )
owl/camel/agents/critic_agent.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import random
15
+ import warnings
16
+ from typing import Any, Dict, Optional, Sequence
17
+
18
+ from colorama import Fore
19
+
20
+ from camel.agents.chat_agent import ChatAgent
21
+ from camel.memories import AgentMemory
22
+ from camel.messages import BaseMessage
23
+ from camel.models import BaseModelBackend
24
+ from camel.responses import ChatAgentResponse
25
+ from camel.utils import get_first_int, print_text_animated
26
+
27
+ # AgentOps decorator setting
28
+ try:
29
+ import os
30
+
31
+ if os.getenv("AGENTOPS_API_KEY") is not None:
32
+ from agentops import track_agent
33
+ else:
34
+ raise ImportError
35
+ except (ImportError, AttributeError):
36
+ from camel.utils import track_agent
37
+
38
+
39
+ @track_agent(name="CriticAgent")
40
+ class CriticAgent(ChatAgent):
41
+ r"""A class for the critic agent that assists in selecting an option.
42
+
43
+ Args:
44
+ system_message (BaseMessage): The system message for the critic
45
+ agent.
46
+ model (BaseModelBackend, optional): The model backend to use for
47
+ generating responses. (default: :obj:`OpenAIModel` with
48
+ `GPT_4O_MINI`)
49
+ message_window_size (int, optional): The maximum number of previous
50
+ messages to include in the context window. If `None`, no windowing
51
+ is performed. (default: :obj:`6`)
52
+ retry_attempts (int, optional): The number of retry attempts if the
53
+ critic fails to return a valid option. (default: :obj:`2`)
54
+ verbose (bool, optional): Whether to print the critic's messages.
55
+ logger_color (Any): The color of the menu options displayed to the
56
+ user. (default: :obj:`Fore.MAGENTA`)
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ system_message: BaseMessage,
62
+ model: Optional[BaseModelBackend] = None,
63
+ memory: Optional[AgentMemory] = None,
64
+ message_window_size: int = 6,
65
+ retry_attempts: int = 2,
66
+ verbose: bool = False,
67
+ logger_color: Any = Fore.MAGENTA,
68
+ ) -> None:
69
+ super().__init__(
70
+ system_message,
71
+ model=model,
72
+ memory=memory,
73
+ message_window_size=message_window_size,
74
+ )
75
+ self.options_dict: Dict[str, str] = dict()
76
+ self.retry_attempts = retry_attempts
77
+ self.verbose = verbose
78
+ self.logger_color = logger_color
79
+
80
+ def flatten_options(self, messages: Sequence[BaseMessage]) -> str:
81
+ r"""Flattens the options to the critic.
82
+
83
+ Args:
84
+ messages (Sequence[BaseMessage]): A list of `BaseMessage` objects.
85
+
86
+ Returns:
87
+ str: A string containing the flattened options to the critic.
88
+ """
89
+ options = [message.content for message in messages]
90
+ flatten_options = (
91
+ f"> Proposals from "
92
+ f"{messages[0].role_name} ({messages[0].role_type}). "
93
+ "Please choose an option:\n"
94
+ )
95
+ for index, option in enumerate(options):
96
+ flatten_options += f"Option {index + 1}:\n{option}\n\n"
97
+ self.options_dict[str(index + 1)] = option
98
+ format = (
99
+ f"Please first enter your choice ([1-{len(self.options_dict)}]) "
100
+ "and then your explanation and comparison: "
101
+ )
102
+ return flatten_options + format
103
+
104
+ def get_option(self, input_message: BaseMessage) -> str:
105
+ r"""Gets the option selected by the critic.
106
+
107
+ Args:
108
+ input_message (BaseMessage): A `BaseMessage` object representing
109
+ the input message.
110
+
111
+ Returns:
112
+ str: The option selected by the critic.
113
+ """
114
+ # TODO: Add support for editing options by the critic.
115
+ msg_content = input_message.content
116
+ i = 0
117
+ while i < self.retry_attempts:
118
+ critic_response = self.step(input_message)
119
+
120
+ if critic_response.msgs is None or len(critic_response.msgs) == 0:
121
+ raise RuntimeError("Got None critic messages.")
122
+ if critic_response.terminated:
123
+ raise RuntimeError("Critic step failed.")
124
+
125
+ critic_msg = critic_response.msg
126
+ if self.verbose:
127
+ print_text_animated(
128
+ self.logger_color + "\n> Critic response: "
129
+ f"\x1b[3m{critic_msg.content}\x1b[0m\n"
130
+ )
131
+ choice = self.parse_critic(critic_msg)
132
+
133
+ if choice in self.options_dict:
134
+ return self.options_dict[choice]
135
+ else:
136
+ input_message = BaseMessage(
137
+ role_name=input_message.role_name,
138
+ role_type=input_message.role_type,
139
+ meta_dict=input_message.meta_dict,
140
+ content="> Invalid choice. Please choose again.\n"
141
+ + msg_content,
142
+ )
143
+ i += 1
144
+ warnings.warn(
145
+ "Critic failed to get a valid option. "
146
+ f"After {self.retry_attempts} attempts. "
147
+ "Returning a random option."
148
+ )
149
+ return random.choice(list(self.options_dict.values()))
150
+
151
+ def parse_critic(self, critic_msg: BaseMessage) -> Optional[str]:
152
+ r"""Parses the critic's message and extracts the choice.
153
+
154
+ Args:
155
+ critic_msg (BaseMessage): A `BaseMessage` object representing the
156
+ critic's response.
157
+
158
+ Returns:
159
+ Optional[str]: The critic's choice as a string, or None if the
160
+ message could not be parsed.
161
+ """
162
+ choice = str(get_first_int(critic_msg.content))
163
+ return choice
164
+
165
+ def reduce_step(
166
+ self,
167
+ input_messages: Sequence[BaseMessage],
168
+ ) -> ChatAgentResponse:
169
+ r"""Performs one step of the conversation by flattening options to the
170
+ critic, getting the option, and parsing the choice.
171
+
172
+ Args:
173
+ input_messages (Sequence[BaseMessage]): A list of BaseMessage
174
+ objects.
175
+
176
+ Returns:
177
+ ChatAgentResponse: A `ChatAgentResponse` object includes the
178
+ critic's choice.
179
+ """
180
+ meta_chat_message = BaseMessage(
181
+ role_name=input_messages[0].role_name,
182
+ role_type=input_messages[0].role_type,
183
+ meta_dict=input_messages[0].meta_dict,
184
+ content="",
185
+ )
186
+
187
+ flatten_options = self.flatten_options(input_messages)
188
+ if self.verbose:
189
+ print_text_animated(
190
+ self.logger_color + f"\x1b[3m{flatten_options}\x1b[0m\n"
191
+ )
192
+ input_msg = meta_chat_message.create_new_instance(flatten_options)
193
+
194
+ option = self.get_option(input_msg)
195
+ output_msg = meta_chat_message.create_new_instance(option)
196
+
197
+ # TODO: The return `info` can be improved.
198
+ return ChatAgentResponse(
199
+ msgs=[output_msg],
200
+ terminated=False,
201
+ info={},
202
+ )
owl/camel/agents/deductive_reasoner_agent.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import re
15
+ from typing import Dict, List, Optional, Union
16
+
17
+ from camel.agents.chat_agent import ChatAgent
18
+ from camel.logger import get_logger
19
+ from camel.messages import BaseMessage
20
+ from camel.models import BaseModelBackend
21
+ from camel.prompts import TextPrompt
22
+ from camel.types import RoleType
23
+
24
+ logger = get_logger(__name__)
25
+
26
+ # AgentOps decorator setting
27
+ try:
28
+ import os
29
+
30
+ if os.getenv("AGENTOPS_API_KEY") is not None:
31
+ from agentops import track_agent
32
+ else:
33
+ raise ImportError
34
+ except (ImportError, AttributeError):
35
+ from camel.utils import track_agent
36
+
37
+
38
+ @track_agent(name="DeductiveReasonerAgent")
39
+ class DeductiveReasonerAgent(ChatAgent):
40
+ r"""An agent responsible for deductive reasoning. Model of deductive
41
+ reasoning:
42
+ - L: A ⊕ C -> q * B
43
+ - A represents the known starting state.
44
+ - B represents the known target state.
45
+ - C represents the conditions required to transition from A to B.
46
+ - Q represents the quality or effectiveness of the transition from
47
+ A to B.
48
+ - L represents the path or process from A to B.
49
+
50
+ Args:
51
+ model (BaseModelBackend, optional): The model backend to use for
52
+ generating responses. (default: :obj:`OpenAIModel` with
53
+ `GPT_4O_MINI`)
54
+ """
55
+
56
+ def __init__(
57
+ self,
58
+ model: Optional[BaseModelBackend] = None,
59
+ ) -> None:
60
+ system_message = BaseMessage(
61
+ role_name="Insight Agent",
62
+ role_type=RoleType.ASSISTANT,
63
+ meta_dict=None,
64
+ content="You assign roles based on tasks.",
65
+ )
66
+ super().__init__(system_message, model=model)
67
+
68
+ def deduce_conditions_and_quality(
69
+ self,
70
+ starting_state: str,
71
+ target_state: str,
72
+ role_descriptions_dict: Optional[Dict[str, str]] = None,
73
+ ) -> Dict[str, Union[List[str], Dict[str, str]]]:
74
+ r"""Derives the conditions and quality from the starting state and the
75
+ target state based on the model of the deductive reasoning and the
76
+ knowledge base. It can optionally consider the roles involved in the
77
+ scenario, which allows tailoring the output more closely to the AI
78
+ agent's environment.
79
+
80
+ Args:
81
+ starting_state (str): The initial or starting state from which
82
+ conditions are deduced.
83
+ target_state (str): The target state of the task.
84
+ role_descriptions_dict (Optional[Dict[str, str]], optional): The
85
+ descriptions of the roles. (default: :obj:`None`)
86
+ role_descriptions_dict (Optional[Dict[str, str]], optional): A
87
+ dictionary describing the roles involved in the scenario. This
88
+ is optional and can be used to provide a context for the
89
+ CAMEL's role-playing, enabling the generation of more relevant
90
+ and tailored conditions and quality assessments. This could be
91
+ generated using a `RoleAssignmentAgent()` or defined manually
92
+ by the user.
93
+
94
+ Returns:
95
+ Dict[str, Union[List[str], Dict[str, str]]]: A dictionary with the
96
+ extracted data from the message. The dictionary contains three
97
+ keys:
98
+ - 'conditions': A list where each key is a condition ID and
99
+ each value is the corresponding condition text.
100
+ - 'labels': A list of label strings extracted from the message.
101
+ - 'quality': A string of quality assessment strings extracted
102
+ from the message.
103
+ """
104
+ self.reset()
105
+
106
+ deduce_prompt = """You are a deductive reasoner. You are tasked to
107
+ complete the TASK based on the THOUGHT OF DEDUCTIVE REASONING, the
108
+ STARTING STATE A and the TARGET STATE B. You are given the CONTEXT
109
+ CONTENT to help you complete the TASK.
110
+ Your answer MUST strictly adhere to the structure of ANSWER TEMPLATE, ONLY
111
+ fill in the BLANKs, and DO NOT alter or modify any other part of the template
112
+
113
+ ===== MODELING OF DEDUCTIVE REASONING =====
114
+ You are tasked with understanding a mathematical model based on the components
115
+ ${A, B, C, Q, L}$. In this model: ``L: A ⊕ C -> q * B``.
116
+ - $A$ represents the known starting state.
117
+ - $B$ represents the known target state.
118
+ - $C$ represents the conditions required to transition from $A$ to $B$.
119
+ - $Q$ represents the quality or effectiveness of the transition from $A$ to
120
+ $B$.
121
+ - $L$ represents the path or process from $A$ to $B$.
122
+
123
+ ===== THOUGHT OF DEDUCTIVE REASONING =====
124
+ 1. Define the Parameters of A and B:
125
+ - Characterization: Before delving into transitions, thoroughly understand
126
+ the nature and boundaries of both $A$ and $B$. This includes the type,
127
+ properties, constraints, and possible interactions between the two.
128
+ - Contrast and Compare: Highlight the similarities and differences between
129
+ $A$ and $B$. This comparative analysis will give an insight into what
130
+ needs changing and what remains constant.
131
+ 2. Historical & Empirical Analysis:
132
+ - Previous Transitions according to the Knowledge Base of GPT: (if
133
+ applicable) Extract conditions and patterns from the historical instances
134
+ where a similar transition from a state comparable to $A$ moved towards
135
+ $B$.
136
+ - Scientific Principles: (if applicable) Consider the underlying
137
+ scientific principles governing or related to the states and their
138
+ transition. For example, if $A$ and $B$ are physical states, laws of
139
+ physics might apply.
140
+ 3. Logical Deduction of Conditions ($C$):
141
+ - Direct Path Analysis: What are the immediate and direct conditions
142
+ required to move from $A$ to $B$?
143
+ - Intermediate States: Are there states between $A$ and $B$ that must be
144
+ traversed or can be used to make the transition smoother or more
145
+ efficient? If yes, what is the content?
146
+ - Constraints & Limitations: Identify potential barriers or restrictions
147
+ in moving from $A$ to $B$. These can be external (e.g., environmental
148
+ factors) or internal (properties of $A$ or $B$).
149
+ - Resource and Information Analysis: What resources and information are
150
+ required for the transition? This could be time, entity, factor, code
151
+ language, software platform, unknowns, etc.
152
+ - External Influences: Consider socio-economic, political, or
153
+ environmental factors (if applicable) that could influence the transition
154
+ conditions.
155
+ - Creative/Heuristic Reasoning: Open your mind to multiple possible $C$'s,
156
+ no matter how unconventional they might seem. Utilize analogies,
157
+ metaphors, or brainstorming techniques to envision possible conditions or
158
+ paths from $A$ to $B$.
159
+ - The conditions $C$ should be multiple but in one sentence. And each
160
+ condition should be concerned with one aspect/entity.
161
+ 4. Entity/Label Recognition of Conditions ($C$):
162
+ - Identify and categorize entities of Conditions ($C$) such as the names,
163
+ locations, dates, specific technical terms or contextual parameters that
164
+ might be associated with events, innovations post-2022.
165
+ - The output of the entities/labels will be used as tags or labels for
166
+ semantic similarity searches. The entities/labels may be the words, or
167
+ phrases, each of them should contain valuable, high information entropy
168
+ information, and should be independent.
169
+ - Ensure that the identified entities are formatted in a manner suitable
170
+ for database indexing and retrieval. Organize the entities into
171
+ categories, and combine the category with its instance into a continuous
172
+ phrase, without using colons or other separators.
173
+ - Format these entities for database indexing: output the category rather
174
+ than its instance/content into a continuous phrase. For example, instead
175
+ of "Jan. 02", identify it as "Event time".
176
+ 5. Quality Assessment ($Q$):
177
+ - Efficiency: How efficient is the transition from $A$ to $B$, which
178
+ measures the resources used versus the desired outcome?
179
+ - Effectiveness: Did the transition achieve the desired outcome or was the
180
+ target state achieved as intended?
181
+ - Safety & Risks: Assess any risks associated with the transition and the
182
+ measures to mitigate them.
183
+ - Feedback Mechanisms: Incorporate feedback loops to continuously monitor
184
+ and adjust the quality of transition, making it more adaptive.
185
+ 6. Iterative Evaluation:
186
+ - Test & Refine: Based on the initially deduced conditions and assessed
187
+ quality, iterate the process to refine and optimize the transition. This
188
+ might involve tweaking conditions, employing different paths, or changing
189
+ resources.
190
+ - Feedback Integration: Use feedback to make improvements and increase the
191
+ quality of the transition.
192
+ 7. Real-world scenarios often present challenges that may not be captured by
193
+ models and frameworks. While using the model, maintain an adaptive mindset:
194
+ - Scenario Exploration: Continuously imagine various possible scenarios,
195
+ both positive and negative, to prepare for unexpected events.
196
+ - Flexibility: Be prepared to modify conditions ($C$) or alter the path/
197
+ process ($L$) if unforeseen challenges arise.
198
+ - Feedback Integration: Rapidly integrate feedback from actual
199
+ implementations to adjust the model's application, ensuring relevancy and
200
+ effectiveness.
201
+
202
+ ===== TASK =====
203
+ Given the starting state $A$ and the target state $B$, assuming that a path
204
+ $L$ always exists between $A$ and $B$, how can one deduce or identify the
205
+ necessary conditions $C$ and the quality $Q$ of the transition?
206
+
207
+ ===== STARTING STATE $A$ =====
208
+ {starting_state}
209
+
210
+ ===== TARGET STATE $B$ =====
211
+ {target_state}
212
+
213
+ {role_with_description_prompt}
214
+ ===== ANSWER TEMPLATE =====
215
+ - Characterization and comparison of $A$ and $B$:\n<BLANK>
216
+ - Historical & Empirical Analysis:\n<BLANK>/None
217
+ - Logical Deduction of Conditions ($C$) (multiple conditions can be deduced):
218
+ condition <NUM>:
219
+ <BLANK>.
220
+ - Entity/Label Recognition of Conditions:\n[<BLANK>, <BLANK>, ...] (include
221
+ square brackets)
222
+ - Quality Assessment ($Q$) (do not use symbols):
223
+ <BLANK>.
224
+ - Iterative Evaluation:\n<BLANK>/None"""
225
+
226
+ if role_descriptions_dict is not None:
227
+ role_names = role_descriptions_dict.keys()
228
+ role_with_description_prompt = (
229
+ "===== ROLES WITH DESCRIPTIONS =====\n"
230
+ + "\n".join(
231
+ f"{role_name}:\n{role_descriptions_dict[role_name]}\n"
232
+ for role_name in role_names
233
+ )
234
+ + "\n\n"
235
+ )
236
+ else:
237
+ role_with_description_prompt = ""
238
+ deduce_prompt = TextPrompt(deduce_prompt)
239
+
240
+ deduce = deduce_prompt.format(
241
+ starting_state=starting_state,
242
+ target_state=target_state,
243
+ role_with_description_prompt=role_with_description_prompt,
244
+ )
245
+
246
+ conditions_and_quality_generation_msg = BaseMessage.make_user_message(
247
+ role_name="Deductive Reasoner", content=deduce
248
+ )
249
+
250
+ response = self.step(
251
+ input_message=conditions_and_quality_generation_msg
252
+ )
253
+
254
+ if response.terminated:
255
+ raise RuntimeError(
256
+ "Deduction failed. Error:\n" + f"{response.info}"
257
+ )
258
+ msg: BaseMessage = response.msg
259
+ logger.info(f"Message content:\n{msg.content}")
260
+
261
+ # Extract the conditions from the message
262
+ conditions_dict = {
263
+ f"condition {i}": cdt.replace("<", "")
264
+ .replace(">", "")
265
+ .strip()
266
+ .strip('\n')
267
+ for i, cdt in re.findall(
268
+ r"condition (\d+):\s*(.+?)(?=condition \d+|- Entity)",
269
+ msg.content,
270
+ re.DOTALL,
271
+ )
272
+ }
273
+
274
+ # Extract the labels from the message
275
+ labels = [
276
+ label.strip().strip('\n').strip("\"'")
277
+ for label in re.findall(
278
+ r"Entity/Label Recognition of Conditions:\n\[(.+?)\]",
279
+ msg.content,
280
+ re.DOTALL,
281
+ )[0].split(",")
282
+ ]
283
+
284
+ # Extract the quality from the message
285
+ quality = next(
286
+ q.strip().strip('\n')
287
+ for q in re.findall(
288
+ r"Quality Assessment \(\$Q\$\) \(do not use symbols\):"
289
+ r"\n(.+?)- Iterative",
290
+ msg.content,
291
+ re.DOTALL,
292
+ )
293
+ )
294
+
295
+ # Convert them into JSON format
296
+ conditions_and_quality_json: Dict[
297
+ str, Union[List[str], Dict[str, str]]
298
+ ] = {}
299
+ conditions_and_quality_json["conditions"] = conditions_dict
300
+ conditions_and_quality_json["labels"] = labels
301
+ conditions_and_quality_json["evaluate_quality"] = quality
302
+
303
+ return conditions_and_quality_json
owl/camel/agents/embodied_agent.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import Any, List, Optional
15
+
16
+ from colorama import Fore
17
+
18
+ from camel.agents.chat_agent import ChatAgent
19
+ from camel.agents.tool_agents.base import BaseToolAgent
20
+ from camel.interpreters import (
21
+ BaseInterpreter,
22
+ InternalPythonInterpreter,
23
+ SubprocessInterpreter,
24
+ )
25
+ from camel.messages import BaseMessage
26
+ from camel.models import BaseModelBackend
27
+ from camel.responses import ChatAgentResponse
28
+ from camel.utils import print_text_animated
29
+
30
+ # AgentOps decorator setting
31
+ try:
32
+ import os
33
+
34
+ if os.getenv("AGENTOPS_API_KEY") is not None:
35
+ from agentops import track_agent
36
+ else:
37
+ raise ImportError
38
+ except (ImportError, AttributeError):
39
+ from camel.utils import track_agent
40
+
41
+
42
+ @track_agent(name="EmbodiedAgent")
43
+ class EmbodiedAgent(ChatAgent):
44
+ r"""Class for managing conversations of CAMEL Embodied Agents.
45
+
46
+ Args:
47
+ system_message (BaseMessage): The system message for the chat agent.
48
+ model (BaseModelBackend, optional): The model backend to use for
49
+ generating responses. (default: :obj:`OpenAIModel` with
50
+ `GPT_4O_MINI`)
51
+ message_window_size (int, optional): The maximum number of previous
52
+ messages to include in the context window. If `None`, no windowing
53
+ is performed. (default: :obj:`None`)
54
+ tool_agents (List[BaseToolAgent], optional): The tools agents to use in
55
+ the embodied agent. (default: :obj:`None`)
56
+ code_interpreter (BaseInterpreter, optional): The code interpreter to
57
+ execute codes. If `code_interpreter` and `tool_agent` are both
58
+ `None`, default to `SubProcessInterpreter`. If `code_interpreter`
59
+ is `None` and `tool_agents` is not `None`, default to
60
+ `InternalPythonInterpreter`. (default: :obj:`None`)
61
+ verbose (bool, optional): Whether to print the critic's messages.
62
+ logger_color (Any): The color of the logger displayed to the user.
63
+ (default: :obj:`Fore.MAGENTA`)
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ system_message: BaseMessage,
69
+ model: Optional[BaseModelBackend] = None,
70
+ message_window_size: Optional[int] = None,
71
+ tool_agents: Optional[List[BaseToolAgent]] = None,
72
+ code_interpreter: Optional[BaseInterpreter] = None,
73
+ verbose: bool = False,
74
+ logger_color: Any = Fore.MAGENTA,
75
+ ) -> None:
76
+ self.tool_agents = tool_agents
77
+ self.code_interpreter: BaseInterpreter
78
+ if code_interpreter is not None:
79
+ self.code_interpreter = code_interpreter
80
+ elif self.tool_agents:
81
+ self.code_interpreter = InternalPythonInterpreter()
82
+ else:
83
+ self.code_interpreter = SubprocessInterpreter()
84
+
85
+ if self.tool_agents:
86
+ system_message = self._set_tool_agents(system_message)
87
+ self.verbose = verbose
88
+ self.logger_color = logger_color
89
+ super().__init__(
90
+ system_message=system_message,
91
+ model=model,
92
+ message_window_size=message_window_size,
93
+ )
94
+
95
+ def _set_tool_agents(self, system_message: BaseMessage) -> BaseMessage:
96
+ action_space_prompt = self._get_tool_agents_prompt()
97
+ result_message = system_message.create_new_instance(
98
+ content=system_message.content.format(
99
+ action_space=action_space_prompt
100
+ )
101
+ )
102
+ if self.tool_agents is not None:
103
+ self.code_interpreter.update_action_space(
104
+ {tool.name: tool for tool in self.tool_agents}
105
+ )
106
+ return result_message
107
+
108
+ def _get_tool_agents_prompt(self) -> str:
109
+ r"""Returns the action space prompt.
110
+
111
+ Returns:
112
+ str: The action space prompt.
113
+ """
114
+ if self.tool_agents is not None:
115
+ return "\n".join(
116
+ [
117
+ f"*** {tool.name} ***:\n {tool.description}"
118
+ for tool in self.tool_agents
119
+ ]
120
+ )
121
+ else:
122
+ return ""
123
+
124
+ def get_tool_agent_names(self) -> List[str]:
125
+ r"""Returns the names of tool agents.
126
+
127
+ Returns:
128
+ List[str]: The names of tool agents.
129
+ """
130
+ if self.tool_agents is not None:
131
+ return [tool.name for tool in self.tool_agents]
132
+ else:
133
+ return []
134
+
135
+ # ruff: noqa: E501
136
+ def step(self, input_message: BaseMessage) -> ChatAgentResponse: # type: ignore[override]
137
+ r"""Performs a step in the conversation.
138
+
139
+ Args:
140
+ input_message (BaseMessage): The input message.
141
+
142
+ Returns:
143
+ ChatAgentResponse: A struct containing the output messages,
144
+ a boolean indicating whether the chat session has terminated,
145
+ and information about the chat session.
146
+ """
147
+ response = super().step(input_message)
148
+
149
+ if response.msgs is None or len(response.msgs) == 0:
150
+ raise RuntimeError("Got None output messages.")
151
+ if response.terminated:
152
+ raise RuntimeError(f"{self.__class__.__name__} step failed.")
153
+
154
+ # NOTE: Only single output messages are supported
155
+ explanations, codes = response.msg.extract_text_and_code_prompts()
156
+
157
+ if self.verbose:
158
+ for explanation, code in zip(explanations, codes):
159
+ print_text_animated(
160
+ self.logger_color + f"> Explanation:\n{explanation}"
161
+ )
162
+ print_text_animated(self.logger_color + f"> Code:\n{code}")
163
+
164
+ if len(explanations) > len(codes):
165
+ print_text_animated(
166
+ self.logger_color + f"> Explanation:\n{explanations[-1]}"
167
+ )
168
+
169
+ content = response.msg.content
170
+
171
+ if codes is not None:
172
+ try:
173
+ content = "\n> Executed Results:\n"
174
+ for block_idx, code in enumerate(codes):
175
+ executed_output = self.code_interpreter.run(
176
+ code, code.code_type
177
+ )
178
+ content += (
179
+ f"Executing code block {block_idx}: {{\n"
180
+ + executed_output
181
+ + "}\n"
182
+ )
183
+ except InterruptedError as e:
184
+ content = (
185
+ f"\n> Running code fail: {e}\n"
186
+ "Please regenerate the code."
187
+ )
188
+
189
+ # TODO: Handle errors
190
+ content = input_message.content + f"\n> Embodied Actions:\n{content}"
191
+ message = BaseMessage(
192
+ input_message.role_name,
193
+ input_message.role_type,
194
+ input_message.meta_dict,
195
+ content,
196
+ )
197
+ return ChatAgentResponse(
198
+ msgs=[message],
199
+ terminated=response.terminated,
200
+ info=response.info,
201
+ )
owl/camel/agents/knowledge_graph_agent.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import TYPE_CHECKING, Optional, Union
15
+
16
+ if TYPE_CHECKING:
17
+ from unstructured.documents.elements import Element
18
+
19
+ from camel.agents import ChatAgent
20
+ from camel.messages import BaseMessage
21
+ from camel.models import BaseModelBackend
22
+ from camel.prompts import TextPrompt
23
+ from camel.storages.graph_storages.graph_element import (
24
+ GraphElement,
25
+ Node,
26
+ Relationship,
27
+ )
28
+ from camel.types import RoleType
29
+
30
+ # AgentOps decorator setting
31
+ try:
32
+ import os
33
+
34
+ if os.getenv("AGENTOPS_API_KEY") is not None:
35
+ from agentops import track_agent
36
+ else:
37
+ raise ImportError
38
+ except (ImportError, AttributeError):
39
+ from camel.utils import track_agent
40
+
41
+
42
+ text_prompt = """
43
+ You are tasked with extracting nodes and relationships from given content and
44
+ structures them into Node and Relationship objects. Here's the outline of what
45
+ you needs to do:
46
+
47
+ Content Extraction:
48
+ You should be able to process input content and identify entities mentioned
49
+ within it.
50
+ Entities can be any noun phrases or concepts that represent distinct entities
51
+ in the context of the given content.
52
+
53
+ Node Extraction:
54
+ For each identified entity, you should create a Node object.
55
+ Each Node object should have a unique identifier (id) and a type (type).
56
+ Additional properties associated with the node can also be extracted and
57
+ stored.
58
+
59
+ Relationship Extraction:
60
+ You should identify relationships between entities mentioned in the content.
61
+ For each relationship, create a Relationship object.
62
+ A Relationship object should have a subject (subj) and an object (obj) which
63
+ are Node objects representing the entities involved in the relationship.
64
+ Each relationship should also have a type (type), and additional properties if
65
+ applicable.
66
+
67
+ Output Formatting:
68
+ The extracted nodes and relationships should be formatted as instances of the
69
+ provided Node and Relationship classes.
70
+ Ensure that the extracted data adheres to the structure defined by the classes.
71
+ Output the structured data in a format that can be easily validated against
72
+ the provided code.
73
+
74
+ Instructions for you:
75
+ Read the provided content thoroughly.
76
+ Identify distinct entities mentioned in the content and categorize them as
77
+ nodes.
78
+ Determine relationships between these entities and represent them as directed
79
+ relationships.
80
+ Provide the extracted nodes and relationships in the specified format below.
81
+ Example for you:
82
+
83
+ Example Content:
84
+ "John works at XYZ Corporation. He is a software engineer. The company is
85
+ located in New York City."
86
+
87
+ Expected Output:
88
+
89
+ Nodes:
90
+
91
+ Node(id='John', type='Person')
92
+ Node(id='XYZ Corporation', type='Organization')
93
+ Node(id='New York City', type='Location')
94
+
95
+ Relationships:
96
+
97
+ Relationship(subj=Node(id='John', type='Person'), obj=Node(id='XYZ
98
+ Corporation', type='Organization'), type='WorksAt')
99
+ Relationship(subj=Node(id='John', type='Person'), obj=Node(id='New York City',
100
+ type='Location'), type='ResidesIn')
101
+
102
+ ===== TASK =====
103
+ Please extracts nodes and relationships from given content and structures them
104
+ into Node and Relationship objects.
105
+
106
+ {task}
107
+ """
108
+
109
+
110
+ @track_agent(name="KnowledgeGraphAgent")
111
+ class KnowledgeGraphAgent(ChatAgent):
112
+ r"""An agent that can extract node and relationship information for
113
+ different entities from given `Element` content.
114
+
115
+ Attributes:
116
+ task_prompt (TextPrompt): A prompt for the agent to extract node and
117
+ relationship information for different entities.
118
+ """
119
+
120
+ def __init__(
121
+ self,
122
+ model: Optional[BaseModelBackend] = None,
123
+ ) -> None:
124
+ r"""Initialize the `KnowledgeGraphAgent`.
125
+
126
+ Args:
127
+ model (BaseModelBackend, optional): The model backend to use for
128
+ generating responses. (default: :obj:`OpenAIModel` with
129
+ `GPT_4O_MINI`)
130
+ """
131
+ system_message = BaseMessage(
132
+ role_name="Graphify",
133
+ role_type=RoleType.ASSISTANT,
134
+ meta_dict=None,
135
+ content="Your mission is to transform unstructured content "
136
+ "into structured graph data. Extract nodes and relationships with "
137
+ "precision, and let the connections unfold. Your graphs will "
138
+ "illuminate the hidden connections within the chaos of "
139
+ "information.",
140
+ )
141
+ super().__init__(system_message, model=model)
142
+
143
+ def run(
144
+ self,
145
+ element: "Element",
146
+ parse_graph_elements: bool = False,
147
+ ) -> Union[str, GraphElement]:
148
+ r"""Run the agent to extract node and relationship information.
149
+
150
+ Args:
151
+ element (Element): The input element.
152
+ parse_graph_elements (bool, optional): Whether to parse into
153
+ `GraphElement`. Defaults to `False`.
154
+
155
+ Returns:
156
+ Union[str, GraphElement]: The extracted node and relationship
157
+ information. If `parse_graph_elements` is `True` then return
158
+ `GraphElement`, else return `str`.
159
+ """
160
+ self.reset()
161
+ self.element = element
162
+
163
+ knowledge_graph_prompt = TextPrompt(text_prompt)
164
+ knowledge_graph_generation = knowledge_graph_prompt.format(
165
+ task=str(element)
166
+ )
167
+
168
+ knowledge_graph_generation_msg = BaseMessage.make_user_message(
169
+ role_name="Graphify", content=knowledge_graph_generation
170
+ )
171
+
172
+ response = self.step(input_message=knowledge_graph_generation_msg)
173
+
174
+ content = response.msg.content
175
+
176
+ if parse_graph_elements:
177
+ content = self._parse_graph_elements(content)
178
+
179
+ return content
180
+
181
+ def _validate_node(self, node: Node) -> bool:
182
+ r"""Validate if the object is a valid Node.
183
+
184
+ Args:
185
+ node (Node): Object to be validated.
186
+
187
+ Returns:
188
+ bool: True if the object is a valid Node, False otherwise.
189
+ """
190
+ return (
191
+ isinstance(node, Node)
192
+ and isinstance(node.id, (str, int))
193
+ and isinstance(node.type, str)
194
+ )
195
+
196
+ def _validate_relationship(self, relationship: Relationship) -> bool:
197
+ r"""Validate if the object is a valid Relationship.
198
+
199
+ Args:
200
+ relationship (Relationship): Object to be validated.
201
+
202
+ Returns:
203
+ bool: True if the object is a valid Relationship, False otherwise.
204
+ """
205
+ return (
206
+ isinstance(relationship, Relationship)
207
+ and self._validate_node(relationship.subj)
208
+ and self._validate_node(relationship.obj)
209
+ and isinstance(relationship.type, str)
210
+ )
211
+
212
+ def _parse_graph_elements(self, input_string: str) -> GraphElement:
213
+ r"""Parses graph elements from given content.
214
+
215
+ Args:
216
+ input_string (str): The input content.
217
+
218
+ Returns:
219
+ GraphElement: The parsed graph elements.
220
+ """
221
+ import re
222
+
223
+ # Regular expressions to extract nodes and relationships
224
+ node_pattern = r"Node\(id='(.*?)', type='(.*?)'\)"
225
+ rel_pattern = (
226
+ r"Relationship\(subj=Node\(id='(.*?)', type='(.*?)'\), "
227
+ r"obj=Node\(id='(.*?)', type='(.*?)'\), type='(.*?)'\)"
228
+ )
229
+
230
+ nodes = {}
231
+ relationships = []
232
+
233
+ # Extract nodes
234
+ for match in re.finditer(node_pattern, input_string):
235
+ id, type = match.groups()
236
+ properties = {'source': 'agent_created'}
237
+ if id not in nodes:
238
+ node = Node(id=id, type=type, properties=properties)
239
+ if self._validate_node(node):
240
+ nodes[id] = node
241
+
242
+ # Extract relationships
243
+ for match in re.finditer(rel_pattern, input_string):
244
+ subj_id, subj_type, obj_id, obj_type, rel_type = match.groups()
245
+ properties = {'source': 'agent_created'}
246
+ if subj_id in nodes and obj_id in nodes:
247
+ subj = nodes[subj_id]
248
+ obj = nodes[obj_id]
249
+ relationship = Relationship(
250
+ subj=subj, obj=obj, type=rel_type, properties=properties
251
+ )
252
+ if self._validate_relationship(relationship):
253
+ relationships.append(relationship)
254
+
255
+ return GraphElement(
256
+ nodes=list(nodes.values()),
257
+ relationships=relationships,
258
+ source=self.element,
259
+ )
owl/camel/agents/role_assignment_agent.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import re
15
+ from typing import Dict, Optional, Union
16
+
17
+ from camel.agents.chat_agent import ChatAgent
18
+ from camel.messages import BaseMessage
19
+ from camel.models import BaseModelBackend
20
+ from camel.prompts import TextPrompt
21
+ from camel.types import RoleType
22
+
23
+ # AgentOps decorator setting
24
+ try:
25
+ import os
26
+
27
+ if os.getenv("AGENTOPS_API_KEY") is not None:
28
+ from agentops import track_agent
29
+ else:
30
+ raise ImportError
31
+ except (ImportError, AttributeError):
32
+ from camel.utils import track_agent
33
+
34
+
35
+ @track_agent(name="RoleAssignmentAgent")
36
+ class RoleAssignmentAgent(ChatAgent):
37
+ r"""An agent that generates role names based on the task prompt.
38
+
39
+ Args:
40
+ model (BaseModelBackend, optional): The model backend to use for
41
+ generating responses. (default: :obj:`OpenAIModel` with
42
+ `GPT_4O_MINI`)
43
+
44
+ Attributes:
45
+ role_assignment_prompt (TextPrompt): A prompt for the agent to generate
46
+ role names.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ model: Optional[BaseModelBackend] = None,
52
+ ) -> None:
53
+ system_message = BaseMessage(
54
+ role_name="Role Assigner",
55
+ role_type=RoleType.ASSISTANT,
56
+ meta_dict=None,
57
+ content="You assign roles based on tasks.",
58
+ )
59
+ super().__init__(system_message, model=model)
60
+
61
+ def run(
62
+ self,
63
+ task_prompt: Union[str, TextPrompt],
64
+ num_roles: int = 2,
65
+ ) -> Dict[str, str]:
66
+ r"""Generate role names based on the input task prompt.
67
+
68
+ Args:
69
+ task_prompt (Union[str, TextPrompt]): The prompt
70
+ for the task based on which the roles are to be generated.
71
+ num_roles (int, optional): The number of roles to generate.
72
+ (default: :obj:`2`)
73
+
74
+ Returns:
75
+ Dict[str, str]: A dictionary mapping role names to their
76
+ descriptions.
77
+ """
78
+ self.reset()
79
+
80
+ expert_prompt = "===== ANSWER PROMPT =====\n" + "\n".join(
81
+ f"Domain expert {i + 1}: <BLANK>\n"
82
+ f"Associated competencies, characteristics, duties "
83
+ f"and workflows: <BLANK>. End."
84
+ for i in range(num_roles or 0)
85
+ )
86
+ role_assignment_generation_prompt = TextPrompt(
87
+ "You are a role assignment agent, and you're in charge of "
88
+ + "recruiting {num_roles} experts for the following task."
89
+ + "\n==== TASK =====\n {task}\n\n"
90
+ + "Identify the domain experts you'd recruit and detail their "
91
+ + "associated competencies, characteristics, duties and workflows "
92
+ + "to complete the task.\n "
93
+ + "Your answer MUST adhere to the format of ANSWER PROMPT, and "
94
+ + "ONLY answer the BLANKs.\n"
95
+ + expert_prompt
96
+ )
97
+ role_assignment_generation = role_assignment_generation_prompt.format(
98
+ num_roles=num_roles, task=task_prompt
99
+ )
100
+
101
+ role_assignment_generation_msg = BaseMessage.make_user_message(
102
+ role_name="Role Assigner", content=role_assignment_generation
103
+ )
104
+
105
+ response = self.step(input_message=role_assignment_generation_msg)
106
+
107
+ msg = response.msg # type: BaseMessage
108
+ terminated = response.terminated
109
+
110
+ # Distribute the output completions into role names and descriptions
111
+ role_names = [
112
+ desc.replace("<|", "").replace("|>", "")
113
+ for desc in re.findall(
114
+ r"Domain expert \d: (.+?)\nAssociated competencies,",
115
+ msg.content,
116
+ re.DOTALL,
117
+ )
118
+ ]
119
+ role_descriptions = [
120
+ desc.replace("<|", "").replace("|>", "")
121
+ for desc in re.findall(
122
+ r"Associated competencies, characteristics, "
123
+ r"duties and workflows: (.+?) End.",
124
+ msg.content,
125
+ re.DOTALL,
126
+ )
127
+ ]
128
+
129
+ if len(role_names) != num_roles or len(role_descriptions) != num_roles:
130
+ raise RuntimeError(
131
+ "Got None or insufficient information of roles."
132
+ )
133
+ if terminated:
134
+ raise RuntimeError("Role assignment failed.")
135
+
136
+ role_descriptions_dict = {
137
+ role_name: description
138
+ for role_name, description in zip(role_names, role_descriptions)
139
+ }
140
+
141
+ return role_descriptions_dict
owl/camel/agents/search_agent.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import Optional
15
+
16
+ from camel.agents.chat_agent import ChatAgent
17
+ from camel.messages import BaseMessage
18
+ from camel.models import BaseModelBackend
19
+ from camel.prompts import TextPrompt
20
+ from camel.types import RoleType
21
+ from camel.utils import create_chunks
22
+
23
+ # AgentOps decorator setting
24
+ try:
25
+ import os
26
+
27
+ if os.getenv("AGENTOPS_API_KEY") is not None:
28
+ from agentops import track_agent
29
+ else:
30
+ raise ImportError
31
+ except (ImportError, AttributeError):
32
+ from camel.utils import track_agent
33
+
34
+
35
+ @track_agent(name="SearchAgent")
36
+ class SearchAgent(ChatAgent):
37
+ r"""An agent that summarizes text based on a query and evaluates the
38
+ relevance of an answer.
39
+
40
+ Args:
41
+ model (BaseModelBackend, optional): The model backend to use for
42
+ generating responses. (default: :obj:`OpenAIModel` with
43
+ `GPT_4O_MINI`)
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ model: Optional[BaseModelBackend] = None,
49
+ ) -> None:
50
+ system_message = BaseMessage(
51
+ role_name="Assistant",
52
+ role_type=RoleType.ASSISTANT,
53
+ meta_dict=None,
54
+ content="You are a helpful assistant.",
55
+ )
56
+ super().__init__(system_message, model=model)
57
+
58
+ def summarize_text(self, text: str, query: str) -> str:
59
+ r"""Summarize the information from the text, base on the query.
60
+
61
+ Args:
62
+ text (str): Text to summarize.
63
+ query (str): What information you want.
64
+
65
+ Returns:
66
+ str: Strings with information.
67
+ """
68
+ self.reset()
69
+
70
+ summary_prompt = TextPrompt(
71
+ '''Gather information from this text that relative to the
72
+ question, but do not directly answer the question.\nquestion:
73
+ {query}\ntext '''
74
+ )
75
+ summary_prompt = summary_prompt.format(query=query)
76
+ # Max length of each chunk
77
+ max_len = 3000
78
+ results = ""
79
+ chunks = create_chunks(text, max_len)
80
+ # Summarize
81
+ for i, chunk in enumerate(chunks, start=1):
82
+ prompt = summary_prompt + str(i) + ": " + chunk
83
+ user_msg = BaseMessage.make_user_message(
84
+ role_name="User",
85
+ content=prompt,
86
+ )
87
+ result = self.step(user_msg).msg.content
88
+ results += result + "\n"
89
+
90
+ # Final summarization
91
+ final_prompt = TextPrompt(
92
+ '''Here are some summarized texts which split from one text. Using
93
+ the information to answer the question. If can't find the answer,
94
+ you must answer "I can not find the answer to the query" and
95
+ explain why.\n Query:\n{query}.\n\nText:\n'''
96
+ )
97
+ final_prompt = final_prompt.format(query=query)
98
+ prompt = final_prompt + results
99
+
100
+ user_msg = BaseMessage.make_user_message(
101
+ role_name="User",
102
+ content=prompt,
103
+ )
104
+ response = self.step(user_msg).msg.content
105
+
106
+ return response
107
+
108
+ def continue_search(self, query: str, answer: str) -> bool:
109
+ r"""Ask whether to continue search or not based on the provided answer.
110
+
111
+ Args:
112
+ query (str): The question.
113
+ answer (str): The answer to the question.
114
+
115
+ Returns:
116
+ bool: `True` if the user want to continue search, `False`
117
+ otherwise.
118
+ """
119
+ prompt = TextPrompt(
120
+ "Do you think the ANSWER can answer the QUERY? "
121
+ "Use only 'yes' or 'no' to answer.\n"
122
+ "===== QUERY =====\n{query}\n\n"
123
+ "===== ANSWER =====\n{answer}"
124
+ )
125
+ prompt = prompt.format(query=query, answer=answer)
126
+ user_msg = BaseMessage.make_user_message(
127
+ role_name="User",
128
+ content=prompt,
129
+ )
130
+ response = self.step(user_msg).msg.content
131
+ if "yes" in str(response).lower():
132
+ return False
133
+ return True
owl/camel/agents/task_agent.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import Any, Dict, List, Optional, Union
15
+
16
+ from camel.agents.chat_agent import ChatAgent
17
+ from camel.messages import BaseMessage
18
+ from camel.models import BaseModelBackend
19
+ from camel.prompts import PromptTemplateGenerator, TextPrompt
20
+ from camel.types import RoleType, TaskType
21
+ from camel.utils import get_task_list
22
+
23
+ # AgentOps decorator setting
24
+ try:
25
+ import os
26
+
27
+ if os.getenv("AGENTOPS_API_KEY") is not None:
28
+ from agentops import track_agent
29
+ else:
30
+ raise ImportError
31
+ except (ImportError, AttributeError):
32
+ from camel.utils import track_agent
33
+
34
+
35
+ @track_agent(name="TaskSpecifyAgent")
36
+ class TaskSpecifyAgent(ChatAgent):
37
+ r"""An agent that specifies a given task prompt by prompting the user to
38
+ provide more details.
39
+
40
+ Attributes:
41
+ DEFAULT_WORD_LIMIT (int): The default word limit for the task prompt.
42
+ task_specify_prompt (TextPrompt): The prompt for specifying the task.
43
+
44
+ Args:
45
+ model (BaseModelBackend, optional): The model backend to use for
46
+ generating responses. (default: :obj:`OpenAIModel` with
47
+ `GPT_4O_MINI`)
48
+ task_type (TaskType, optional): The type of task for which to generate
49
+ a prompt. (default: :obj:`TaskType.AI_SOCIETY`)
50
+ task_specify_prompt (Union[str, TextPrompt], optional): The prompt for
51
+ specifying the task. (default: :obj:`None`)
52
+ word_limit (int, optional): The word limit for the task prompt.
53
+ (default: :obj:`50`)
54
+ output_language (str, optional): The language to be output by the
55
+ agent. (default: :obj:`None`)
56
+ """
57
+
58
+ DEFAULT_WORD_LIMIT = 50
59
+
60
+ def __init__(
61
+ self,
62
+ model: Optional[BaseModelBackend] = None,
63
+ task_type: TaskType = TaskType.AI_SOCIETY,
64
+ task_specify_prompt: Optional[Union[str, TextPrompt]] = None,
65
+ word_limit: int = DEFAULT_WORD_LIMIT,
66
+ output_language: Optional[str] = None,
67
+ ) -> None:
68
+ self.task_specify_prompt: Union[str, TextPrompt]
69
+ if task_specify_prompt is None:
70
+ task_specify_prompt_template = (
71
+ PromptTemplateGenerator().get_task_specify_prompt(task_type)
72
+ )
73
+
74
+ self.task_specify_prompt = task_specify_prompt_template.format(
75
+ word_limit=word_limit
76
+ )
77
+ else:
78
+ self.task_specify_prompt = TextPrompt(task_specify_prompt)
79
+
80
+ system_message = BaseMessage(
81
+ role_name="Task Specifier",
82
+ role_type=RoleType.ASSISTANT,
83
+ meta_dict=None,
84
+ content="You can make a task more specific.",
85
+ )
86
+
87
+ super().__init__(
88
+ system_message,
89
+ model=model,
90
+ output_language=output_language,
91
+ )
92
+
93
+ def run(
94
+ self,
95
+ task_prompt: Union[str, TextPrompt],
96
+ meta_dict: Optional[Dict[str, Any]] = None,
97
+ ) -> TextPrompt:
98
+ r"""Specify the given task prompt by providing more details.
99
+
100
+ Args:
101
+ task_prompt (Union[str, TextPrompt]): The original task
102
+ prompt.
103
+ meta_dict (Dict[str, Any], optional): A dictionary containing
104
+ additional information to include in the prompt.
105
+ (default: :obj:`None`)
106
+
107
+ Returns:
108
+ TextPrompt: The specified task prompt.
109
+ """
110
+ self.reset()
111
+ task_specify_prompt = self.task_specify_prompt.format(task=task_prompt)
112
+
113
+ if meta_dict is not None:
114
+ task_specify_prompt = task_specify_prompt.format(**meta_dict)
115
+ task_msg = BaseMessage.make_user_message(
116
+ role_name="Task Specifier", content=task_specify_prompt
117
+ )
118
+ specifier_response = self.step(task_msg)
119
+
120
+ if specifier_response.terminated:
121
+ raise RuntimeError("Task specification failed.")
122
+ if len(specifier_response.msgs) == 0:
123
+ raise RuntimeError("Got no specification message.")
124
+
125
+ specified_task_msg = specifier_response.msgs[0]
126
+
127
+ return TextPrompt(specified_task_msg.content)
128
+
129
+
130
+ @track_agent(name="TaskPlannerAgent")
131
+ class TaskPlannerAgent(ChatAgent):
132
+ r"""An agent that helps divide a task into subtasks based on the input
133
+ task prompt.
134
+
135
+ Attributes:
136
+ task_planner_prompt (TextPrompt): A prompt for the agent to divide
137
+ the task into subtasks.
138
+
139
+ Args:
140
+ model (BaseModelBackend, optional): The model backend to use for
141
+ generating responses. (default: :obj:`OpenAIModel` with
142
+ `GPT_4O_MINI`)
143
+ output_language (str, optional): The language to be output by the
144
+ agent. (default: :obj:`None`)
145
+ """
146
+
147
+ def __init__(
148
+ self,
149
+ model: Optional[BaseModelBackend] = None,
150
+ output_language: Optional[str] = None,
151
+ ) -> None:
152
+ self.task_planner_prompt = TextPrompt(
153
+ "Divide this task into subtasks: {task}. Be concise."
154
+ )
155
+ system_message = BaseMessage(
156
+ role_name="Task Planner",
157
+ role_type=RoleType.ASSISTANT,
158
+ meta_dict=None,
159
+ content="You are a helpful task planner.",
160
+ )
161
+
162
+ super().__init__(
163
+ system_message,
164
+ model=model,
165
+ output_language=output_language,
166
+ )
167
+
168
+ def run(
169
+ self,
170
+ task_prompt: Union[str, TextPrompt],
171
+ ) -> TextPrompt:
172
+ r"""Generate subtasks based on the input task prompt.
173
+
174
+ Args:
175
+ task_prompt (Union[str, TextPrompt]): The prompt for the task to
176
+ be divided into subtasks.
177
+
178
+ Returns:
179
+ TextPrompt: A prompt for the subtasks generated by the agent.
180
+ """
181
+ # TODO: Maybe include roles information.
182
+ self.reset()
183
+ task_planner_prompt = self.task_planner_prompt.format(task=task_prompt)
184
+
185
+ task_msg = BaseMessage.make_user_message(
186
+ role_name="Task Planner", content=task_planner_prompt
187
+ )
188
+
189
+ task_response = self.step(task_msg)
190
+
191
+ if task_response.terminated:
192
+ raise RuntimeError("Task planning failed.")
193
+ if len(task_response.msgs) == 0:
194
+ raise RuntimeError("Got no task planning message.")
195
+
196
+ sub_tasks_msg = task_response.msgs[0]
197
+ return TextPrompt(sub_tasks_msg.content)
198
+
199
+
200
+ @track_agent(name="TaskCreationAgent")
201
+ class TaskCreationAgent(ChatAgent):
202
+ r"""An agent that helps create new tasks based on the objective
203
+ and last completed task. Compared to :obj:`TaskPlannerAgent`,
204
+ it's still a task planner, but it has more context information
205
+ like last task and incomplete task list. Modified from
206
+ `BabyAGI <https://github.com/yoheinakajima/babyagi>`_.
207
+
208
+ Attributes:
209
+ task_creation_prompt (TextPrompt): A prompt for the agent to
210
+ create new tasks.
211
+
212
+ Args:
213
+ role_name (str): The role name of the Agent to create the task.
214
+ objective (Union[str, TextPrompt]): The objective of the Agent to
215
+ perform the task.
216
+ model (BaseModelBackend, optional): The LLM backend to use for
217
+ generating responses. (default: :obj:`OpenAIModel` with
218
+ `GPT_4O_MINI`)
219
+ output_language (str, optional): The language to be output by the
220
+ agent. (default: :obj:`None`)
221
+ message_window_size (int, optional): The maximum number of previous
222
+ messages to include in the context window. If `None`, no windowing
223
+ is performed. (default: :obj:`None`)
224
+ max_task_num (int, optional): The maximum number of planned
225
+ tasks in one round. (default: :obj:3)
226
+ """
227
+
228
+ def __init__(
229
+ self,
230
+ role_name: str,
231
+ objective: Union[str, TextPrompt],
232
+ model: Optional[BaseModelBackend] = None,
233
+ output_language: Optional[str] = None,
234
+ message_window_size: Optional[int] = None,
235
+ max_task_num: Optional[int] = 3,
236
+ ) -> None:
237
+ task_creation_prompt = TextPrompt(
238
+ """Create new a task with the following objective: {objective}.
239
+ Never forget you are a Task Creator of {role_name}.
240
+ You must instruct me based on my expertise and your needs to solve the task.
241
+ You should consider past solved tasks and in-progress tasks: {task_list}.
242
+ The new created tasks must not overlap with these past tasks.
243
+ The result must be a numbered list in the format:
244
+
245
+ #. First Task
246
+ #. Second Task
247
+ #. Third Task
248
+
249
+ You can only give me up to {max_task_num} tasks at a time. \
250
+ Each task should be concise, concrete and doable for a {role_name}.
251
+ You should make task plan and not ask me questions.
252
+ If you think no new tasks are needed right now, write "No tasks to add."
253
+ Now start to give me new tasks one by one. No more than three tasks.
254
+ Be concrete.
255
+ """
256
+ )
257
+
258
+ self.task_creation_prompt = task_creation_prompt.format(
259
+ objective=objective, role_name=role_name, max_task_num=max_task_num
260
+ )
261
+ self.objective = objective
262
+
263
+ system_message = BaseMessage(
264
+ role_name="Task Creator",
265
+ role_type=RoleType.ASSISTANT,
266
+ meta_dict=None,
267
+ content="You are a helpful task creator.",
268
+ )
269
+
270
+ super().__init__(
271
+ system_message,
272
+ model=model,
273
+ output_language=output_language,
274
+ message_window_size=message_window_size,
275
+ )
276
+
277
+ def run(
278
+ self,
279
+ task_list: List[str],
280
+ ) -> List[str]:
281
+ r"""Generate subtasks based on the previous task results and
282
+ incomplete task list.
283
+
284
+ Args:
285
+ task_list (List[str]): The completed or in-progress
286
+ tasks which should not overlap with new created tasks.
287
+
288
+ Returns:
289
+ List[str]: The new task list generated by the Agent.
290
+ """
291
+
292
+ if len(task_list) > 0:
293
+ task_creation_prompt = self.task_creation_prompt.format(
294
+ task_list=task_list
295
+ )
296
+ else:
297
+ task_creation_prompt = self.task_creation_prompt.format(
298
+ task_list=""
299
+ )
300
+
301
+ task_msg = BaseMessage.make_user_message(
302
+ role_name="Task Creator", content=task_creation_prompt
303
+ )
304
+ task_response = self.step(task_msg)
305
+
306
+ if task_response.terminated:
307
+ raise RuntimeError("Task creation failed.")
308
+ if len(task_response.msgs) == 0:
309
+ raise RuntimeError("Got no task creation message.")
310
+
311
+ sub_tasks_msg = task_response.msgs[0]
312
+ return get_task_list(sub_tasks_msg.content)
313
+
314
+
315
+ @track_agent(name="TaskPrioritizationAgent")
316
+ class TaskPrioritizationAgent(ChatAgent):
317
+ r"""An agent that helps re-prioritize the task list and
318
+ returns numbered prioritized list. Modified from
319
+ `BabyAGI <https://github.com/yoheinakajima/babyagi>`_.
320
+
321
+ Attributes:
322
+ task_prioritization_prompt (TextPrompt): A prompt for the agent to
323
+ prioritize tasks.
324
+
325
+ Args:
326
+ objective (Union[str, TextPrompt]): The objective of the Agent to
327
+ perform the task.
328
+ model (BaseModelBackend, optional): The LLM backend to use for
329
+ generating responses. (default: :obj:`OpenAIModel` with
330
+ `GPT_4O_MINI`)
331
+ output_language (str, optional): The language to be output by the
332
+ agent. (default: :obj:`None`)
333
+ message_window_size (int, optional): The maximum number of previous
334
+ messages to include in the context window. If `None`, no windowing
335
+ is performed. (default: :obj:`None`)
336
+ """
337
+
338
+ def __init__(
339
+ self,
340
+ objective: Union[str, TextPrompt],
341
+ model: Optional[BaseModelBackend] = None,
342
+ output_language: Optional[str] = None,
343
+ message_window_size: Optional[int] = None,
344
+ ) -> None:
345
+ task_prioritization_prompt = TextPrompt(
346
+ """Prioritize the following tasks : {task_list}.
347
+ Consider the ultimate objective of you: {objective}.
348
+ Tasks should be sorted from highest to lowest priority, where higher-priority \
349
+ tasks are those that act as pre-requisites or are more essential for meeting \
350
+ the objective. Return one task per line in your response.
351
+ Do not remove or modify any tasks.
352
+ The result must be a numbered list in the format:
353
+
354
+ #. First task
355
+ #. Second task
356
+
357
+ The entries must be consecutively numbered, starting with 1.
358
+ The number of each entry must be followed by a period.
359
+ Do not include any headers before your ranked list or follow your list \
360
+ with any other output."""
361
+ )
362
+
363
+ self.task_prioritization_prompt = task_prioritization_prompt.format(
364
+ objective=objective
365
+ )
366
+ self.objective = objective
367
+
368
+ system_message = BaseMessage(
369
+ role_name="Task Prioritizer",
370
+ role_type=RoleType.ASSISTANT,
371
+ meta_dict=None,
372
+ content="You are a helpful task prioritizer.",
373
+ )
374
+
375
+ super().__init__(
376
+ system_message,
377
+ model=model,
378
+ output_language=output_language,
379
+ message_window_size=message_window_size,
380
+ )
381
+
382
+ def run(
383
+ self,
384
+ task_list: List[str],
385
+ ) -> List[str]:
386
+ r"""Prioritize the task list given the agent objective.
387
+
388
+ Args:
389
+ task_list (List[str]): The unprioritized tasks of agent.
390
+
391
+ Returns:
392
+ List[str]: The new prioritized task list generated by the Agent.
393
+ """
394
+ task_prioritization_prompt = self.task_prioritization_prompt.format(
395
+ task_list=task_list
396
+ )
397
+
398
+ task_msg = BaseMessage.make_user_message(
399
+ role_name="Task Prioritizer", content=task_prioritization_prompt
400
+ )
401
+
402
+ task_response = self.step(task_msg)
403
+
404
+ if task_response.terminated:
405
+ raise RuntimeError("Task prioritization failed.")
406
+ if len(task_response.msgs) == 0:
407
+ raise RuntimeError("Got no task prioritization message.")
408
+
409
+ sub_tasks_msg = task_response.msgs[0]
410
+ return get_task_list(sub_tasks_msg.content)
owl/camel/agents/tool_agents/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from .base import BaseToolAgent
15
+ from .hugging_face_tool_agent import HuggingFaceToolAgent
16
+
17
+ __all__ = [
18
+ 'BaseToolAgent',
19
+ 'HuggingFaceToolAgent',
20
+ ]
owl/camel/agents/tool_agents/base.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from camel.agents import BaseAgent
15
+
16
+
17
+ class BaseToolAgent(BaseAgent):
18
+ r"""Creates a :obj:`BaseToolAgent` object with the specified name and
19
+ description.
20
+
21
+ Args:
22
+ name (str): The name of the tool agent.
23
+ description (str): The description of the tool agent.
24
+ """
25
+
26
+ def __init__(self, name: str, description: str) -> None:
27
+ self.name = name
28
+ self.description = description
29
+
30
+ def reset(self) -> None:
31
+ r"""Resets the agent to its initial state."""
32
+ pass
33
+
34
+ def step(self) -> None:
35
+ r"""Performs a single step of the agent."""
36
+ pass
37
+
38
+ def __str__(self) -> str:
39
+ return f"{self.name}: {self.description}"
owl/camel/agents/tool_agents/hugging_face_tool_agent.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import Any, Optional
15
+
16
+ from camel.agents.tool_agents.base import BaseToolAgent
17
+
18
+
19
+ # flake8: noqa :E501
20
+ class HuggingFaceToolAgent(BaseToolAgent):
21
+ r"""Tool agent for calling HuggingFace models. This agent is a wrapper
22
+ around agents from the `transformers` library. For more information
23
+ about the available models, please see the `transformers` documentation
24
+ at https://huggingface.co/docs/transformers/transformers_agents.
25
+
26
+ Args:
27
+ name (str): The name of the agent.
28
+ *args (Any): Additional positional arguments to pass to the underlying
29
+ Agent class.
30
+ remote (bool, optional): Flag indicating whether to run the agent
31
+ remotely. (default: :obj:`True`)
32
+ **kwargs (Any): Additional keyword arguments to pass to the underlying
33
+ Agent class.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ name: str,
39
+ *args: Any,
40
+ remote: bool = True,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ try:
44
+ # TODO: Support other tool agents
45
+ import transformers
46
+ from packaging import version
47
+
48
+ if version.parse(transformers.__version__) < version.parse(
49
+ "4.31.0"
50
+ ):
51
+ raise ValueError(
52
+ "The version of \"transformers\" package should >= 4.31.0"
53
+ )
54
+
55
+ from transformers.tools import OpenAiAgent
56
+ from transformers.tools.agent_types import AgentImage
57
+ except (ImportError, ValueError):
58
+ raise ValueError(
59
+ "Could not import transformers tool agents. "
60
+ "Please setup the environment with "
61
+ "pip install huggingface_hub==0.14.1 transformers==4.31.0 diffusers accelerate==0.20.3 datasets torch soundfile sentencepiece opencv-python"
62
+ )
63
+ self.agent_image_type = AgentImage
64
+ self.agent = OpenAiAgent(*args, **kwargs)
65
+ description = f"""The `{name}` is a tool agent that can perform a variety of tasks including:
66
+ - Document question answering: given a document (such as a PDF) in image format, answer a question on this document
67
+ - Text question answering: given a long text and a question, answer the question in the text
68
+ - Unconditional image captioning: Caption the image!
69
+ - Image question answering: given an image, answer a question on this image
70
+ - Image segmentation: given an image and a prompt, output the segmentation mask of that prompt
71
+ - Speech to text: given an audio recording of a person talking, transcribe the speech into text
72
+ - Text to speech: convert text to speech
73
+ - Zero-shot text classification: given a text and a list of labels, identify to which label the text corresponds the most
74
+ - Text summarization: summarize a long text in one or a few sentences
75
+ - Translation: translate the text into a given language
76
+ - Text downloading: to download a text from a web URL
77
+ - Text to image: generate an image according to a prompt, leveraging stable diffusion
78
+ - Image transformation: modify an image given an initial image and a prompt, leveraging instruct pix2pix stable diffusion
79
+ - Text to video: generate a small video according to a prompt
80
+
81
+ Here are some python code examples of what you can do with this agent:
82
+
83
+ Single execution (step) mode, the single execution method is when using the step() method of the agent:
84
+ ```
85
+ # Text to image
86
+ rivers_and_lakes_image = {name}.step("Draw me a picture of rivers and lakes.")
87
+ rivers_and_lakes_image.save("./rivers_and_lakes_image.png")
88
+
89
+ # Text to image -> Image transformation
90
+ sea_add_island_image = {name}.step("Draw me a picture of the sea then transform the picture to add an island")
91
+ sea_add_island_image.save("./sea_add_island_image.png")
92
+
93
+ # If you'd like to keep a state across executions or to pass non-text objects to the agent,
94
+ # you can do so by specifying variables that you would like the agent to use. For example,
95
+ # you could generate the first image of rivers and lakes, and ask the model to update that picture to add an island by doing the following:
96
+ picture = {name}.step("Generate a picture of rivers and lakes.")
97
+ picture.save("./picture.png")
98
+ updated_picture = {name}.step("Transform the image in `picture` to add an island to it.", picture=picture)
99
+ updated_picture.save("./updated_picture.png")
100
+
101
+ capybara_sea_image = {name}.step("Draw me a picture of the `prompt`", prompt="a capybara swimming in the sea")
102
+ capybara_sea_image.save("./capybara_sea_image.png")
103
+
104
+ # Document question answering
105
+ answer = {name}.step(
106
+ "In the following `document`, where will the TRRF Scientific Advisory Council Meeting take place?",
107
+ document=document,
108
+ )
109
+ print(answer)
110
+
111
+
112
+ # Text to image
113
+ boat_image = {name}.step("Generate an image of a boat in the water")
114
+ boat_image.save("./boat_image.png")
115
+
116
+ # Unconditional image captioning
117
+ boat_image_caption = {name}.step("Can you caption the `boat_image`?", boat_image=boat_image)
118
+ print(boat_image_caption)
119
+
120
+ # Text to image -> Unconditional image captioning -> Text to speech
121
+ boat_audio = {name}.step("Can you generate an image of a boat? Please read out loud the contents of the image afterwards")
122
+
123
+ # Text downloading
124
+ document = {name}.step("Download the text from http://hf.co")
125
+ print(document)
126
+
127
+ # Text summarization
128
+ summary = {name}.step("Summarize the following text: `document`", document=document)
129
+ print(summary)
130
+
131
+ # Text downloading -> Text summarization -> Text to speech
132
+ audio = {name}.step("Read out loud the summary of http://hf.co")
133
+ ```
134
+
135
+ Chat-based execution (chat), the agent also has a chat-based approach, using the chat() method:
136
+ ```
137
+ # Clean the chat history
138
+ {name}.reset()
139
+
140
+ # Text to image
141
+ capybara_image = {name}.chat("Show me an an image of a capybara")
142
+ capybara_image.save("./capybara_image.png")
143
+
144
+ # Image transformation
145
+ transformed_capybara_image = {name}.chat("Transform the image so that it snows")
146
+ transformed_capybara_image.save("./transformed_capybara_image.png")
147
+
148
+ # Image segmentation
149
+ segmented_transformed_capybara_image = {name}.chat("Show me a mask of the snowy capybaras")
150
+ segmented_transformed_capybara_image.save("./segmented_transformed_capybara_image.png")
151
+ ```
152
+ """
153
+ super(HuggingFaceToolAgent, self).__init__(name, description)
154
+ self.remote = remote
155
+
156
+ def reset(self) -> None:
157
+ r"""Resets the chat history of the agent."""
158
+ self.agent.prepare_for_new_chat()
159
+
160
+ def step(
161
+ self,
162
+ *args: Any,
163
+ remote: Optional[bool] = None,
164
+ **kwargs: Any,
165
+ ) -> Any:
166
+ r"""Runs the agent in single execution mode.
167
+
168
+ Args:
169
+ *args (Any): Positional arguments to pass to the agent.
170
+ remote (bool, optional): Flag indicating whether to run the agent
171
+ remotely. Overrides the default setting. (default: :obj:`None`)
172
+ **kwargs (Any): Keyword arguments to pass to the agent.
173
+
174
+ Returns:
175
+ str: The response from the agent.
176
+ """
177
+ if remote is None:
178
+ remote = self.remote
179
+ agent_output = self.agent.run(*args, remote=remote, **kwargs)
180
+ if isinstance(agent_output, self.agent_image_type):
181
+ agent_output = agent_output.to_raw()
182
+ return agent_output
183
+
184
+ def chat(
185
+ self,
186
+ *args: Any,
187
+ remote: Optional[bool] = None,
188
+ **kwargs: Any,
189
+ ) -> Any:
190
+ r"""Runs the agent in a chat conversation mode.
191
+
192
+ Args:
193
+ *args (Any): Positional arguments to pass to the agent.
194
+ remote (bool, optional): Flag indicating whether to run the agent
195
+ remotely. Overrides the default setting. (default: :obj:`None`)
196
+ **kwargs (Any): Keyword arguments to pass to the agent.
197
+
198
+ Returns:
199
+ str: The response from the agent.
200
+ """
201
+ if remote is None:
202
+ remote = self.remote
203
+ agent_output = self.agent.chat(*args, remote=remote, **kwargs)
204
+ if isinstance(agent_output, self.agent_image_type):
205
+ agent_output = agent_output.to_raw()
206
+ return agent_output
owl/camel/benchmarks/__init__.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ from .base import BaseBenchmark
16
+
17
+ __all__ = ["BaseBenchmark"]
owl/camel/benchmarks/base.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+
15
+ import logging
16
+ from abc import ABC, abstractmethod
17
+ from pathlib import Path
18
+ from typing import Any, Dict, List, Literal, Optional
19
+
20
+ from camel.agents import ChatAgent
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class BaseBenchmark(ABC):
26
+ r"""Base class for benchmarks.
27
+
28
+ Attributes:
29
+ name (str): Name of the benchmark.
30
+ data_dir (str): Path to the data directory.
31
+ save_to (str): Path to save the results.
32
+ processes (int): Number of processes to use for parallel
33
+ processing. :(default: :obj:`1`)
34
+ """
35
+
36
+ def __init__(
37
+ self, name: str, data_dir: str, save_to: str, processes: int = 1
38
+ ):
39
+ r"""Initialize the benchmark.
40
+
41
+ Args:
42
+ name (str): Name of the benchmark.
43
+ data_dir (str): Path to the data directory.
44
+ save_to (str): Path to save the results.
45
+ processes (int): Number of processes to use for parallel
46
+ processing. :(default: :obj:`1`)
47
+
48
+ """
49
+ self.name = name
50
+ self.data_dir = Path(data_dir)
51
+ self.processes = processes
52
+ self.save_to = save_to
53
+ if not self.data_dir.exists():
54
+ logger.info(
55
+ f"Data directory {data_dir} does not exist. Creating it."
56
+ )
57
+ self.data_dir.mkdir(parents=True, exist_ok=True)
58
+ if not self.data_dir.is_dir():
59
+ raise NotADirectoryError(
60
+ f"Data directory {data_dir} is not a directory"
61
+ )
62
+ self._data: Dict[str, List[Dict[str, Any]]] = dict()
63
+ self._results: List[Dict[str, Any]] = []
64
+
65
+ @abstractmethod
66
+ def download(self) -> "BaseBenchmark":
67
+ r"""Download the benchmark data.
68
+
69
+ Returns:
70
+ BaseBenchmark: The benchmark instance.
71
+ """
72
+ pass
73
+
74
+ @abstractmethod
75
+ def load(self, force_download: bool = False) -> "BaseBenchmark":
76
+ r"""Load the benchmark data.
77
+
78
+ Args:
79
+ force_download (bool): Whether to force download the data.
80
+
81
+ Returns:
82
+ BaseBenchmark: The benchmark instance.
83
+ """
84
+ pass
85
+
86
+ @property
87
+ def train(self) -> List[Dict[str, Any]]:
88
+ r"""Get the training data.
89
+
90
+ Returns:
91
+ List[Dict[str, Any]]: The training data.
92
+ """
93
+ if not self._data:
94
+ logger.info("Data not loaded. Loading data.")
95
+ self.load()
96
+ return self._data["train"]
97
+
98
+ @property
99
+ def valid(self) -> List[Dict[str, Any]]:
100
+ r"""Get the validation data.
101
+
102
+ Returns:
103
+ List[Dict[str, Any]]: The validation data.
104
+ """
105
+ if not self._data:
106
+ logger.info("Data not loaded. Loading data.")
107
+ self.load()
108
+ return self._data["valid"]
109
+
110
+ @property
111
+ def test(self) -> List[Dict[str, Any]]:
112
+ r"""Get the test data.
113
+
114
+ Returns:
115
+ List[Dict[str, Any]]: The test data.
116
+ """
117
+ if not self._data:
118
+ logger.info("Data not loaded. Loading data.")
119
+ self.load()
120
+ return self._data["test"]
121
+
122
+ @abstractmethod
123
+ def run(
124
+ self,
125
+ agent: ChatAgent,
126
+ on: Literal["train", "valid", "test"],
127
+ randomize: bool = False,
128
+ subset: Optional[int] = None,
129
+ *args,
130
+ **kwargs,
131
+ ) -> "BaseBenchmark":
132
+ r"""Run the benchmark.
133
+
134
+ Args:
135
+ agent (ChatAgent): The chat agent.
136
+ on (str): The data split to run the benchmark on.
137
+ randomize (bool): Whether to randomize the data.
138
+ subset (int): The subset of the data to run the benchmark on.
139
+
140
+ Returns:
141
+ BaseBenchmark: The benchmark instance.
142
+ """
143
+ pass
144
+
145
+ @property
146
+ def results(self) -> List[Dict[str, Any]]:
147
+ r"""Get the results.
148
+
149
+ Returns:
150
+ List[Dict[str, Any]]: The results.
151
+ """
152
+ return self._results
owl/camel/bots/__init__.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from .discord_app import DiscordApp
15
+ from .slack.models import (
16
+ SlackAppMentionEventBody,
17
+ SlackAppMentionEventProfile,
18
+ SlackAuthProfile,
19
+ SlackEventBody,
20
+ SlackEventProfile,
21
+ )
22
+ from .slack.slack_app import SlackApp
23
+ from .telegram_bot import TelegramBot
24
+
25
+ __all__ = [
26
+ 'DiscordApp',
27
+ 'SlackApp',
28
+ 'SlackAppMentionEventBody',
29
+ 'SlackAppMentionEventProfile',
30
+ 'SlackAuthProfile',
31
+ 'SlackEventBody',
32
+ 'SlackEventProfile',
33
+ 'TelegramBot',
34
+ ]
owl/camel/bots/discord_app.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import logging
15
+ import os
16
+ from typing import TYPE_CHECKING, List, Optional
17
+
18
+ from camel.utils import dependencies_required
19
+
20
+ if TYPE_CHECKING:
21
+ from discord import Message
22
+
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class DiscordApp:
28
+ r"""A class representing a Discord app that uses the `discord.py` library
29
+ to interact with Discord servers.
30
+
31
+ This bot can respond to messages in specific channels and only reacts to
32
+ messages that mention the bot.
33
+
34
+ Attributes:
35
+ channel_ids (Optional[List[int]]): A list of allowed channel IDs. If
36
+ provided, the bot will only respond to messages in these channels.
37
+ token (Optional[str]): The Discord bot token used for authentication.
38
+ """
39
+
40
+ @dependencies_required('discord')
41
+ def __init__(
42
+ self,
43
+ channel_ids: Optional[List[int]] = None,
44
+ token: Optional[str] = None,
45
+ ) -> None:
46
+ r"""Initialize the DiscordApp instance by setting up the Discord client
47
+ and event handlers.
48
+
49
+ Args:
50
+ channel_ids (Optional[List[int]]): A list of allowed channel IDs.
51
+ The bot will only respond to messages in these channels if
52
+ provided.
53
+ token (Optional[str]): The Discord bot token for authentication.
54
+ If not provided, the token will be retrieved from the
55
+ environment variable `DISCORD_TOKEN`.
56
+
57
+ Raises:
58
+ ValueError: If the `DISCORD_TOKEN` is not found in environment
59
+ variables.
60
+ """
61
+ self.token = token or os.getenv('DISCORD_TOKEN')
62
+ self.channel_ids = channel_ids
63
+
64
+ if not self.token:
65
+ raise ValueError(
66
+ "`DISCORD_TOKEN` not found in environment variables. Get it"
67
+ " here: `https://discord.com/developers/applications`."
68
+ )
69
+
70
+ import discord
71
+
72
+ intents = discord.Intents.default()
73
+ intents.message_content = True
74
+ self._client = discord.Client(intents=intents)
75
+
76
+ # Register event handlers
77
+ self._client.event(self.on_ready)
78
+ self._client.event(self.on_message)
79
+
80
+ async def start(self):
81
+ r"""Asynchronously start the Discord bot using its token.
82
+
83
+ This method starts the bot and logs into Discord asynchronously using
84
+ the provided token. It should be awaited when used in an async
85
+ environment.
86
+ """
87
+ await self._client.start(self.token)
88
+
89
+ def run(self) -> None:
90
+ r"""Start the Discord bot using its token.
91
+
92
+ This method starts the bot and logs into Discord synchronously using
93
+ the provided token. It blocks execution and keeps the bot running.
94
+ """
95
+ self._client.run(self.token) # type: ignore[arg-type]
96
+
97
+ async def on_ready(self) -> None:
98
+ r"""Event handler that is called when the bot has successfully
99
+ connected to the Discord server.
100
+
101
+ When the bot is ready and logged into Discord, it prints a message
102
+ displaying the bot's username.
103
+ """
104
+ logger.info(f'We have logged in as {self._client.user}')
105
+
106
+ async def on_message(self, message: 'Message') -> None:
107
+ r"""Event handler for processing incoming messages.
108
+
109
+ This method is called whenever a new message is received by the bot. It
110
+ will ignore messages sent by the bot itself, only respond to messages
111
+ in allowed channels (if specified), and only to messages that mention
112
+ the bot.
113
+
114
+ Args:
115
+ message (discord.Message): The message object received from
116
+ Discord.
117
+ """
118
+ # If the message author is the bot itself,
119
+ # do not respond to this message
120
+ if message.author == self._client.user:
121
+ return
122
+
123
+ # If allowed channel IDs are provided,
124
+ # only respond to messages in those channels
125
+ if self.channel_ids and message.channel.id not in self.channel_ids:
126
+ return
127
+
128
+ # Only respond to messages that mention the bot
129
+ if not self._client.user or not self._client.user.mentioned_in(
130
+ message
131
+ ):
132
+ return
133
+
134
+ logger.info(f"Received message: {message.content}")
135
+
136
+ @property
137
+ def client(self):
138
+ return self._client
owl/camel/bots/slack/__init__.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from .models import (
15
+ SlackAppMentionEventBody,
16
+ SlackAppMentionEventProfile,
17
+ SlackAuthProfile,
18
+ SlackEventBody,
19
+ SlackEventProfile,
20
+ )
21
+ from .slack_app import SlackApp
22
+
23
+ __all__ = [
24
+ 'SlackApp',
25
+ 'SlackAppMentionEventBody',
26
+ 'SlackAppMentionEventProfile',
27
+ 'SlackAuthProfile',
28
+ 'SlackEventBody',
29
+ 'SlackEventProfile',
30
+ ]
owl/camel/bots/slack/models.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from typing import Optional
15
+
16
+ from pydantic import BaseModel
17
+
18
+
19
+ class SlackAuthProfile(BaseModel):
20
+ r"""Represents the authorization profile within a Slack event.
21
+
22
+ Events will contain a single, compact authorizations field that shows one
23
+ installation of your app that the event is visible to.
24
+ In other words, lists of authorizations will be truncated to one element.
25
+
26
+ If there's more than one installing party that your app is keeping track
27
+ of, it's best not to rely on the single party listed in authorizations to
28
+ be any particular one.
29
+
30
+ To get a full list of who can see events, call the apps.event.
31
+ authorizations.list method after obtaining an app-level token. Read more on
32
+ the changes here; they have taken effect for existing apps as of
33
+ February 24, 2021.
34
+
35
+ References:
36
+
37
+ - https://api.slack.com/apis/events-api#authorizations
38
+ - https://api.slack.com/changelog/2020-09-15-events-api-truncate-authed-users#no_context
39
+ """
40
+
41
+ enterprise_id: Optional[str] = None
42
+ """The ID of the enterprise associated with the authorization."""
43
+
44
+ team_id: str
45
+ """The ID of the team associated with the authorization."""
46
+
47
+ user_id: str
48
+ """The ID of the user associated with the authorization."""
49
+
50
+ is_bot: bool
51
+ """Whether the authorized user is a bot."""
52
+
53
+ is_enterprise_install: bool
54
+ """Whether the authorization is for an enterprise installation."""
55
+
56
+
57
+ class SlackEventProfile(BaseModel):
58
+ r"""Represents the detailed profile of a Slack event, including user,
59
+ message, and context data.
60
+ """
61
+
62
+ user: str
63
+ """The ID of the user associated with the event."""
64
+
65
+ type: str
66
+ """The type of the event (e.g., 'message')."""
67
+
68
+ ts: str
69
+ """A timestamp representing when the event was triggered."""
70
+
71
+ thread_ts: Optional[str] = None
72
+ """The timestamp of the parent message in a thread."""
73
+
74
+ client_msg_id: str
75
+ """A unique ID generated by the client for the message (if available)."""
76
+
77
+ text: str
78
+ """The message content text."""
79
+
80
+ team: str
81
+ """The ID of the team that the event is associated with."""
82
+
83
+ blocks: list
84
+ """The list of message blocks, providing structured information."""
85
+
86
+ channel: str
87
+ """The ID of the Slack channel where the event happened."""
88
+
89
+ event_ts: str
90
+ """The event-specific timestamp when it occurred."""
91
+
92
+ channel_type: Optional[str]
93
+ """The type of Slack channel (e.g., 'channel', 'im')."""
94
+
95
+
96
+ class SlackEventBody(BaseModel):
97
+ r"""Represents the entire body of a Slack event, including the event
98
+ profile, authorization, and context.
99
+ """
100
+
101
+ token: str
102
+ """The token to verify the source of the event."""
103
+
104
+ team_id: str
105
+ """The ID of the team where the event is happening."""
106
+
107
+ context_team_id: Optional[str]
108
+ """The team ID for the shared channel context, if applicable."""
109
+
110
+ context_enterprise_id: Optional[str] = None
111
+ """The enterprise ID for the shared channel context, if applicable."""
112
+
113
+ api_app_id: str
114
+ """The unique identifier for the Slack app that received the event."""
115
+
116
+ event: SlackEventProfile
117
+ """A detailed profile of the event"""
118
+
119
+ type: str
120
+ """The overall type of event received (e.g., 'event_callback')."""
121
+
122
+ event_id: str
123
+ """A unique identifier assigned to this event by Slack."""
124
+
125
+ event_time: int
126
+ """The timestamp (in seconds) representing when the event was triggered."""
127
+
128
+ authorizations: Optional[list[SlackAuthProfile]] = None
129
+ """An optional list of authorizations that describe which installation can
130
+ see the event."""
131
+
132
+ is_ext_shared_channel: bool
133
+ """Indicates if the event is part of a shared channel between different
134
+ organizations."""
135
+
136
+ event_context: str
137
+ """A unique string representing the context of the event."""
138
+
139
+
140
+ class SlackAppMentionEventProfile(SlackEventProfile):
141
+ r"""Represents the detailed profile of a Slack event where the app was
142
+ mentioned in a message.
143
+ """
144
+
145
+ channel_type: Optional[str] = None
146
+ """The type of Slack channel. it's None for app mentions."""
147
+
148
+
149
+ class SlackAppMentionEventBody(SlackEventBody):
150
+ r"""Represents the entire body of a Slack event where the app was mentioned
151
+ in a message.
152
+ """
153
+
154
+ context_team_id: Optional[str] = None
155
+ """A detailed profile of the event. it's None for app mentions."""
156
+
157
+ event: SlackAppMentionEventProfile
158
+ """A detailed profile of the event"""
owl/camel/bots/slack/slack_app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import logging
15
+ import os
16
+ from typing import TYPE_CHECKING, Any, Dict, Optional
17
+
18
+ from slack_sdk.oauth.installation_store.async_installation_store import (
19
+ AsyncInstallationStore,
20
+ )
21
+ from starlette import requests, responses
22
+
23
+ from camel.bots.slack.models import (
24
+ SlackAppMentionEventBody,
25
+ SlackAppMentionEventProfile,
26
+ SlackEventBody,
27
+ SlackEventProfile,
28
+ )
29
+ from camel.utils import dependencies_required
30
+
31
+ if TYPE_CHECKING:
32
+ from slack_bolt.context.async_context import AsyncBoltContext
33
+ from slack_bolt.context.say.async_say import AsyncSay
34
+ from slack_sdk.web.async_client import AsyncWebClient
35
+
36
+ logging.basicConfig(level=logging.INFO)
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ class SlackApp:
41
+ r"""Represents a Slack app that is powered by a Slack Bolt `AsyncApp`.
42
+
43
+ This class is responsible for initializing and managing the Slack
44
+ application by setting up event handlers, running the app server, and
45
+ handling events such as messages and mentions from Slack.
46
+
47
+ Args:
48
+ token (Optional[str]): Slack API token for authentication.
49
+ scopes (Optional[str]): Slack app scopes for permissions.
50
+ signing_secret (Optional[str]): Signing secret for verifying Slack
51
+ requests.
52
+ client_id (Optional[str]): Slack app client ID.
53
+ client_secret (Optional[str]): Slack app client secret.
54
+ redirect_uri_path (str): The URI path for OAuth redirect, defaults to
55
+ "/slack/oauth_redirect".
56
+ installation_store (Optional[AsyncInstallationStore]): The installation
57
+ store for handling OAuth installations.
58
+ """
59
+
60
+ @dependencies_required('slack_bolt')
61
+ def __init__(
62
+ self,
63
+ token: Optional[str] = None,
64
+ scopes: Optional[str] = None,
65
+ signing_secret: Optional[str] = None,
66
+ client_id: Optional[str] = None,
67
+ client_secret: Optional[str] = None,
68
+ redirect_uri_path: str = "/slack/oauth_redirect",
69
+ installation_store: Optional[AsyncInstallationStore] = None,
70
+ ) -> None:
71
+ r"""Initializes the SlackApp instance by setting up the Slack Bolt app
72
+ and configuring event handlers and OAuth settings.
73
+
74
+ Args:
75
+ token (Optional[str]): The Slack API token.
76
+ scopes (Optional[str]): The scopes for Slack app permissions.
77
+ signing_secret (Optional[str]): The signing secret for verifying
78
+ requests.
79
+ client_id (Optional[str]): The Slack app client ID.
80
+ client_secret (Optional[str]): The Slack app client secret.
81
+ redirect_uri_path (str): The URI path for handling OAuth redirects
82
+ (default is "/slack/oauth_redirect").
83
+ installation_store (Optional[AsyncInstallationStore]): An optional
84
+ installation store for OAuth installations.
85
+ """
86
+ from slack_bolt.adapter.starlette.async_handler import (
87
+ AsyncSlackRequestHandler,
88
+ )
89
+ from slack_bolt.app.async_app import AsyncApp
90
+ from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
91
+
92
+ self.token: Optional[str] = token or os.getenv("SLACK_TOKEN")
93
+ self.scopes: Optional[str] = scopes or os.getenv("SLACK_SCOPES")
94
+ self.signing_secret: Optional[str] = signing_secret or os.getenv(
95
+ "SLACK_SIGNING_SECRET"
96
+ )
97
+ self.client_id: Optional[str] = client_id or os.getenv(
98
+ "SLACK_CLIENT_ID"
99
+ )
100
+ self.client_secret: Optional[str] = client_secret or os.getenv(
101
+ "SLACK_CLIENT_SECRET"
102
+ )
103
+
104
+ if not all([self.token, self.scopes, self.signing_secret]):
105
+ raise ValueError(
106
+ "`SLACK_TOKEN`, `SLACK_SCOPES`, and `SLACK_SIGNING_SECRET` "
107
+ "environment variables must be set. Get it here: "
108
+ "`https://api.slack.com/apps`."
109
+ )
110
+
111
+ # Setup OAuth settings if client ID and secret are provided
112
+ if self.client_id and self.client_secret:
113
+ self._app = AsyncApp(
114
+ oauth_settings=AsyncOAuthSettings(
115
+ client_id=self.client_id,
116
+ client_secret=self.client_secret,
117
+ scopes=self.scopes,
118
+ redirect_uri_path=redirect_uri_path,
119
+ ),
120
+ logger=logger,
121
+ signing_secret=self.signing_secret,
122
+ installation_store=installation_store,
123
+ token=self.token,
124
+ )
125
+ else:
126
+ # Initialize Slack Bolt AsyncApp with settings
127
+ self._app = AsyncApp(
128
+ logger=logger,
129
+ signing_secret=self.signing_secret,
130
+ installation_store=installation_store,
131
+ token=self.token,
132
+ )
133
+
134
+ self._handler = AsyncSlackRequestHandler(self._app)
135
+ self.setup_handlers()
136
+
137
+ def setup_handlers(self) -> None:
138
+ r"""Sets up the event handlers for Slack events, such as `app_mention`
139
+ and `message`.
140
+
141
+ This method registers the `app_mention` and `on_message` event handlers
142
+ with the Slack Bolt app to respond to Slack events.
143
+ """
144
+ self._app.event("app_mention")(self.app_mention)
145
+ self._app.event("message")(self.on_message)
146
+
147
+ def run(
148
+ self,
149
+ port: int = 3000,
150
+ path: str = "/slack/events",
151
+ host: Optional[str] = None,
152
+ ) -> None:
153
+ r"""Starts the Slack Bolt app server to listen for incoming Slack
154
+ events.
155
+
156
+ Args:
157
+ port (int): The port on which the server should run (default is
158
+ 3000).
159
+ path (str): The endpoint path for receiving Slack events (default
160
+ is "/slack/events").
161
+ host (Optional[str]): The hostname to bind the server (default is
162
+ None).
163
+ """
164
+ self._app.start(port=port, path=path, host=host)
165
+
166
+ async def handle_request(
167
+ self, request: requests.Request
168
+ ) -> responses.Response:
169
+ r"""Handles incoming requests from Slack through the request handler.
170
+
171
+ Args:
172
+ request (Request): A Starlette request object representing the
173
+ incoming request.
174
+
175
+ Returns:
176
+ The response generated by the Slack Bolt handler.
177
+ """
178
+ return await self._handler.handle(request)
179
+
180
+ async def app_mention(
181
+ self,
182
+ context: "AsyncBoltContext",
183
+ client: "AsyncWebClient",
184
+ event: Dict[str, Any],
185
+ body: Dict[str, Any],
186
+ say: "AsyncSay",
187
+ ) -> None:
188
+ r"""Event handler for `app_mention` events.
189
+
190
+ This method is triggered when someone mentions the app in Slack.
191
+
192
+ Args:
193
+ context (AsyncBoltContext): The Slack Bolt context for the event.
194
+ client (AsyncWebClient): The Slack Web API client.
195
+ event (Dict[str, Any]): The event data for the app mention.
196
+ body (Dict[str, Any]): The full request body from Slack.
197
+ say (AsyncSay): A function to send a response back to the channel.
198
+ """
199
+ event_profile = SlackAppMentionEventProfile(**event)
200
+ event_body = SlackAppMentionEventBody(**body)
201
+
202
+ logger.info(f"app_mention, context: {context}")
203
+ logger.info(f"app_mention, client: {client}")
204
+ logger.info(f"app_mention, event_profile: {event_profile}")
205
+ logger.info(f"app_mention, event_body: {event_body}")
206
+ logger.info(f"app_mention, say: {say}")
207
+
208
+ async def on_message(
209
+ self,
210
+ context: "AsyncBoltContext",
211
+ client: "AsyncWebClient",
212
+ event: Dict[str, Any],
213
+ body: Dict[str, Any],
214
+ say: "AsyncSay",
215
+ ) -> None:
216
+ r"""Event handler for `message` events.
217
+
218
+ This method is triggered when the app receives a message in Slack.
219
+
220
+ Args:
221
+ context (AsyncBoltContext): The Slack Bolt context for the event.
222
+ client (AsyncWebClient): The Slack Web API client.
223
+ event (Dict[str, Any]): The event data for the message.
224
+ body (Dict[str, Any]): The full request body from Slack.
225
+ say (AsyncSay): A function to send a response back to the channel.
226
+ """
227
+ await context.ack()
228
+
229
+ event_profile = SlackEventProfile(**event)
230
+ event_body = SlackEventBody(**body)
231
+
232
+ logger.info(f"on_message, context: {context}")
233
+ logger.info(f"on_message, client: {client}")
234
+ logger.info(f"on_message, event_profile: {event_profile}")
235
+ logger.info(f"on_message, event_body: {event_body}")
236
+ logger.info(f"on_message, say: {say}")
237
+
238
+ logger.info(f"Received message: {event_profile.text}")
239
+
240
+ def mention_me(
241
+ self, context: "AsyncBoltContext", body: SlackEventBody
242
+ ) -> bool:
243
+ r"""Check if the bot is mentioned in the message.
244
+
245
+ Args:
246
+ context (AsyncBoltContext): The Slack Bolt context for the event.
247
+ body (SlackEventBody): The body of the Slack event.
248
+
249
+ Returns:
250
+ bool: True if the bot is mentioned in the message, False otherwise.
251
+ """
252
+ message = body.event.text
253
+ bot_user_id = context.bot_user_id
254
+ mention = f"<@{bot_user_id}>"
255
+ return mention in message
owl/camel/bots/telegram_bot.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ import os
15
+ from typing import TYPE_CHECKING, Optional
16
+
17
+ from camel.agents import ChatAgent
18
+ from camel.messages import BaseMessage
19
+ from camel.utils import dependencies_required
20
+
21
+ # Conditionally import telebot types only for type checking
22
+ if TYPE_CHECKING:
23
+ from telebot.types import ( # type: ignore[import-untyped]
24
+ Message,
25
+ )
26
+
27
+
28
+ class TelegramBot:
29
+ r"""Represents a Telegram bot that is powered by an agent.
30
+
31
+ Attributes:
32
+ chat_agent (ChatAgent): Chat agent that will power the bot.
33
+ telegram_token (str, optional): The bot token.
34
+ """
35
+
36
+ @dependencies_required('telebot')
37
+ def __init__(
38
+ self,
39
+ chat_agent: ChatAgent,
40
+ telegram_token: Optional[str] = None,
41
+ ) -> None:
42
+ self.chat_agent = chat_agent
43
+
44
+ if not telegram_token:
45
+ self.token = os.getenv('TELEGRAM_TOKEN')
46
+ if not self.token:
47
+ raise ValueError(
48
+ "`TELEGRAM_TOKEN` not found in environment variables. "
49
+ "Get it from t.me/BotFather."
50
+ )
51
+ else:
52
+ self.token = telegram_token
53
+
54
+ import telebot # type: ignore[import-untyped]
55
+
56
+ self.bot = telebot.TeleBot(token=self.token)
57
+
58
+ # Register the message handler within the constructor
59
+ self.bot.message_handler(func=lambda message: True)(self.on_message)
60
+
61
+ def run(self) -> None:
62
+ r"""Start the Telegram bot."""
63
+ print("Telegram bot is running...")
64
+ self.bot.infinity_polling()
65
+
66
+ def on_message(self, message: 'Message') -> None:
67
+ r"""Handles incoming messages from the user.
68
+
69
+ Args:
70
+ message (types.Message): The incoming message object.
71
+ """
72
+ self.chat_agent.reset()
73
+
74
+ if not message.text:
75
+ return
76
+
77
+ user_msg = BaseMessage.make_user_message(
78
+ role_name="User", content=message.text
79
+ )
80
+ assistant_response = self.chat_agent.step(user_msg)
81
+
82
+ self.bot.reply_to(message, assistant_response.msg.content)
owl/camel/configs/__init__.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+ # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
+ from .anthropic_config import ANTHROPIC_API_PARAMS, AnthropicConfig
15
+ from .base_config import BaseConfig
16
+ from .cohere_config import COHERE_API_PARAMS, CohereConfig
17
+ from .deepseek_config import DEEPSEEK_API_PARAMS, DeepSeekConfig
18
+ from .gemini_config import Gemini_API_PARAMS, GeminiConfig
19
+ from .groq_config import GROQ_API_PARAMS, GroqConfig
20
+ from .litellm_config import LITELLM_API_PARAMS, LiteLLMConfig
21
+ from .mistral_config import MISTRAL_API_PARAMS, MistralConfig
22
+ from .nvidia_config import NVIDIA_API_PARAMS, NvidiaConfig
23
+ from .ollama_config import OLLAMA_API_PARAMS, OllamaConfig
24
+ from .openai_config import OPENAI_API_PARAMS, ChatGPTConfig
25
+ from .qwen_config import QWEN_API_PARAMS, QwenConfig
26
+ from .reka_config import REKA_API_PARAMS, RekaConfig
27
+ from .samba_config import (
28
+ SAMBA_CLOUD_API_PARAMS,
29
+ SAMBA_VERSE_API_PARAMS,
30
+ SambaCloudAPIConfig,
31
+ SambaVerseAPIConfig,
32
+ )
33
+ from .togetherai_config import TOGETHERAI_API_PARAMS, TogetherAIConfig
34
+ from .vllm_config import VLLM_API_PARAMS, VLLMConfig
35
+ from .yi_config import YI_API_PARAMS, YiConfig
36
+ from .zhipuai_config import ZHIPUAI_API_PARAMS, ZhipuAIConfig
37
+
38
+ __all__ = [
39
+ 'BaseConfig',
40
+ 'ChatGPTConfig',
41
+ 'OPENAI_API_PARAMS',
42
+ 'AnthropicConfig',
43
+ 'ANTHROPIC_API_PARAMS',
44
+ 'GROQ_API_PARAMS',
45
+ 'GroqConfig',
46
+ 'LiteLLMConfig',
47
+ 'LITELLM_API_PARAMS',
48
+ 'NvidiaConfig',
49
+ 'NVIDIA_API_PARAMS',
50
+ 'OllamaConfig',
51
+ 'OLLAMA_API_PARAMS',
52
+ 'ZhipuAIConfig',
53
+ 'ZHIPUAI_API_PARAMS',
54
+ 'GeminiConfig',
55
+ 'Gemini_API_PARAMS',
56
+ 'VLLMConfig',
57
+ 'VLLM_API_PARAMS',
58
+ 'MistralConfig',
59
+ 'MISTRAL_API_PARAMS',
60
+ 'RekaConfig',
61
+ 'REKA_API_PARAMS',
62
+ 'SambaVerseAPIConfig',
63
+ 'SAMBA_VERSE_API_PARAMS',
64
+ 'SambaCloudAPIConfig',
65
+ 'SAMBA_CLOUD_API_PARAMS',
66
+ 'TogetherAIConfig',
67
+ 'TOGETHERAI_API_PARAMS',
68
+ 'CohereConfig',
69
+ 'COHERE_API_PARAMS',
70
+ 'YiConfig',
71
+ 'YI_API_PARAMS',
72
+ 'QwenConfig',
73
+ 'QWEN_API_PARAMS',
74
+ 'DeepSeekConfig',
75
+ 'DEEPSEEK_API_PARAMS',
76
+ ]