Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccef61b2b9 | ||
|
|
487670d207 | ||
|
|
a109ac98ed | ||
|
|
d928a95ed1 | ||
|
|
ffa6873a86 | ||
|
|
db2eb6fbac | ||
|
|
68d471bfc6 | ||
|
|
03c71368f5 | ||
|
|
2fd83289fd | ||
|
|
7dd60a8946 | ||
|
|
ccaf1fae52 | ||
|
|
4db2ec5911 | ||
|
|
34e9baccf3 | ||
|
|
3febcfcc04 | ||
|
|
bb15199b4e | ||
|
|
4ec54b690c | ||
|
|
3f7408301a | ||
|
|
da7dde23d4 | ||
|
|
5517f53f8a | ||
|
|
9f128f8445 | ||
|
|
f02f096356 | ||
|
|
e0ffa95951 | ||
|
|
357f0e7bb1 | ||
|
|
475807a1c6 | ||
|
|
e06acd65a6 | ||
|
|
893f9ec2d8 | ||
|
|
1274a0c646 | ||
|
|
e4ae8162a0 | ||
|
|
dce9074969 | ||
|
|
b7da34ff93 | ||
|
|
c360240259 | ||
|
|
e68dc2212c | ||
|
|
e63d772959 | ||
|
|
45700d77ab | ||
|
|
f09cb8a7b5 | ||
|
|
49a36de149 | ||
|
|
309a481a69 | ||
|
|
a11445e7c0 | ||
|
|
d5c431a609 | ||
|
|
cfe19e637f | ||
|
|
cea68fed86 | ||
|
|
273f4bd858 | ||
|
|
55d5ff39ff | ||
|
|
6049db1f24 | ||
|
|
b479dd0b2f | ||
|
|
8b199869c7 | ||
|
|
b9a954a058 | ||
|
|
82aecd0aae | ||
|
|
812af254b5 | ||
|
|
a05596c416 | ||
|
|
cb251f93b5 | ||
|
|
317f1521eb | ||
|
|
9c9824c05e | ||
|
|
564a09c96d | ||
|
|
7fb5fa75ee | ||
|
|
d01ae7217f | ||
|
|
2ff93cef3c | ||
|
|
f7f33d0c79 | ||
|
|
50142b48f8 | ||
|
|
1b17b95e8c | ||
|
|
4de527aa42 | ||
|
|
de22f7218a | ||
|
|
fdb4c887c3 | ||
|
|
c6fe2865b6 | ||
|
|
5ea0b4a895 | ||
|
|
523e7f8271 | ||
|
|
c93ff60900 | ||
|
|
26e5159c1d | ||
|
|
0f09ef92dd | ||
|
|
5ac7df1854 | ||
|
|
7f3682e884 | ||
|
|
600bff8da4 | ||
|
|
c9f6d76d30 | ||
|
|
c030b55521 | ||
|
|
83c595144b | ||
|
|
3a9514629a | ||
|
|
ae8ff0640b | ||
|
|
b883b003be | ||
|
|
89f91f7831 | ||
|
|
a4f56d582b | ||
|
|
56d5ec2d37 | ||
|
|
ad9ca5e7cb | ||
|
|
36b26b43c9 | ||
|
|
3d1f42351f | ||
|
|
1a2b790f2b | ||
|
|
81b772df9a | ||
|
|
7c4f283a05 | ||
|
|
66c460adad | ||
|
|
efa1bbaecb | ||
|
|
ad21f66a44 | ||
|
|
d5a07c11db | ||
|
|
f4b0af1eb1 | ||
|
|
79400b8f52 | ||
|
|
e1706d97f2 | ||
|
|
023c183e85 | ||
|
|
77d6e23c45 | ||
|
|
ca1f12b91b | ||
|
|
f2eda0e7d7 | ||
|
|
153bd21910 | ||
|
|
a3ca718131 | ||
|
|
4342677344 | ||
|
|
26421190b1 | ||
|
|
906dc18060 | ||
|
|
2cb7ac34ce | ||
|
|
6d1edf9184 | ||
|
|
e1d55649d5 | ||
|
|
6a7a3d623e | ||
|
|
83e93dcf43 | ||
|
|
a32cf60958 | ||
|
|
14f42b638d | ||
|
|
89c3ecea68 | ||
|
|
c65e6321f5 | ||
|
|
d1954ff326 | ||
|
|
592c7e6915 | ||
|
|
ecbdcaa57e | ||
|
|
aad1b426f0 | ||
|
|
ce81133560 | ||
|
|
454e68033c | ||
|
|
8da3d2b3f8 | ||
|
|
59795c3dc3 | ||
|
|
6adc04200e | ||
|
|
f3c71d6f19 | ||
|
|
8cedd9123b | ||
|
|
7313294c69 | ||
|
|
424c5c4f7b | ||
|
|
e0f0c5c7f6 | ||
|
|
2eb97e6724 | ||
|
|
5b491ddbf7 | ||
|
|
164b741d57 | ||
|
|
dfda888e57 | ||
|
|
a6c4b5ab3d | ||
|
|
488a645cf4 | ||
|
|
68bfc0ecef | ||
|
|
f4feb42dda | ||
|
|
49fab1b488 | ||
|
|
06e6b2798b | ||
|
|
09ce9a882a | ||
|
|
2ff7e90cea | ||
|
|
a9c1f5b790 | ||
|
|
9fe561085b | ||
|
|
92f9b93353 | ||
|
|
4198e932ca | ||
|
|
00d1b01624 | ||
|
|
75e417129d | ||
|
|
46b5edfd3b | ||
|
|
e66f535dd3 | ||
|
|
4d5a532b23 | ||
|
|
369850b86d | ||
|
|
7553d9dbb6 | ||
|
|
ed2a9cc204 | ||
|
|
9a1b2b93f6 | ||
|
|
3c66eb646e | ||
|
|
82cf54706b | ||
|
|
8cfb2d1246 | ||
|
|
aa9177df0c | ||
|
|
864fb36af5 | ||
|
|
f0aaa06d15 | ||
|
|
60795111b0 | ||
|
|
469551c2b5 | ||
|
|
6eafeb15a4 | ||
|
|
d3e95712fd | ||
|
|
fecc01e230 | ||
|
|
d75735ecb0 | ||
|
|
70fcb0d70d | ||
|
|
6eee5cf350 | ||
|
|
21bf224fef | ||
|
|
139f8cdc11 | ||
|
|
ff8fdddbdc | ||
|
|
93ebb9468c | ||
|
|
a1e71fd0ce | ||
|
|
208bb5e93d | ||
|
|
416d9d00ad | ||
|
|
3550c4a448 | ||
|
|
3af3791f54 | ||
|
|
196841db50 | ||
|
|
bb67df8f42 | ||
|
|
26e9dbcd40 | ||
|
|
42f9485a39 | ||
|
|
6fb9ce67c0 | ||
|
|
06ddc45955 | ||
|
|
93c8f0f8e4 | ||
|
|
a667f89c12 | ||
|
|
8991aaae8d | ||
|
|
97708c7947 | ||
|
|
688e94d97c | ||
|
|
a09b6bf8aa | ||
|
|
f2ce720a3d | ||
|
|
a5c5061a2f | ||
|
|
5321dcc3ba | ||
|
|
ac5118c4e3 | ||
|
|
a4f28cec5d | ||
|
|
95f43be2af | ||
|
|
ff9c1576b6 | ||
|
|
4f7e30b498 | ||
|
|
d6aba5fd39 | ||
|
|
f70606b5ec | ||
|
|
92e2e8c0d6 | ||
|
|
23dce5b886 | ||
|
|
80a3391b84 | ||
|
|
8f8c2104a2 | ||
|
|
7f4c96371e | ||
|
|
46c3b7c17e | ||
|
|
319a4389ac | ||
|
|
32b3908aa3 | ||
|
|
f798e4936c | ||
|
|
e534faf115 | ||
|
|
a93dbbfb5c | ||
|
|
f0cca0ed02 | ||
|
|
0f7ad9b741 | ||
|
|
b9fc781f28 | ||
|
|
c47e921a3b | ||
|
|
7890b4b3ca | ||
|
|
f69ceb5025 | ||
|
|
aa75d276dc | ||
|
|
ffebcccd32 | ||
|
|
3b201c82db | ||
|
|
704509560a | ||
|
|
8c496d2bc2 | ||
|
|
5992fdd659 | ||
|
|
d476cf91dc | ||
|
|
02d28b4322 | ||
|
|
9e47e2bf4f | ||
|
|
95f5b9df68 | ||
|
|
1ffaf4689e | ||
|
|
140f7842cc | ||
|
|
b5311b2651 | ||
|
|
e99851fba3 | ||
|
|
3acbae5ea0 | ||
|
|
56b5db7df3 | ||
|
|
698ed78acc | ||
|
|
9c3330b45d | ||
|
|
9e5b2c5ed7 | ||
|
|
11fa4aed48 | ||
|
|
919cf1437d | ||
|
|
1b5a55ccf2 | ||
|
|
b3efd09fb3 | ||
|
|
617927c291 | ||
|
|
0ce492d083 | ||
|
|
4cf1beb49f | ||
|
|
c41c259cd6 | ||
|
|
a3e95abfde | ||
|
|
164d2b21e9 | ||
|
|
b34e343535 | ||
|
|
8ccb6f4d77 | ||
|
|
36b80dc758 | ||
|
|
927d09ffb5 | ||
|
|
a5ecd2d389 | ||
|
|
039ea71678 | ||
|
|
a0b09410b3 | ||
|
|
3dbef96cf0 | ||
|
|
69f276955a | ||
|
|
e56e5a4b3d | ||
|
|
6b31516cd9 | ||
|
|
61d83e6614 | ||
|
|
4d0130c297 | ||
|
|
7331cb7cb2 | ||
|
|
8c431c690e | ||
|
|
f60406d0f1 | ||
|
|
7e18d78805 | ||
|
|
cd1833f3ad | ||
|
|
45818b1eba | ||
|
|
9561ca95a1 | ||
|
|
875ab3bd8e | ||
|
|
1dd8e0a016 | ||
|
|
5862c98f3e | ||
|
|
ddb533a255 | ||
|
|
cc951d4745 | ||
|
|
557f7aa333 | ||
|
|
e0eee90202 | ||
|
|
d8ded2d456 | ||
|
|
862a78276f | ||
|
|
e69cff0735 | ||
|
|
f42a31578e | ||
|
|
90894f806a | ||
|
|
5c9ada9468 | ||
|
|
cf1d3d0ba1 | ||
|
|
58d52ad61f | ||
|
|
0c3a07f208 | ||
|
|
44e0508ae5 | ||
|
|
4676b817e9 | ||
|
|
32b17c3373 | ||
|
|
7e95498f7a | ||
|
|
4712d39427 | ||
|
|
4c87353db4 | ||
|
|
d1b20a1446 | ||
|
|
18f23db0fa | ||
|
|
8106cff45f | ||
|
|
75ac1631c4 | ||
|
|
021ef0cdc1 | ||
|
|
c995d2a47c | ||
|
|
de76fe14ea | ||
|
|
ca50b1f2d0 | ||
|
|
a4cfa9c651 | ||
|
|
430d032095 | ||
|
|
0bf813e865 | ||
|
|
a65f54e9a1 | ||
|
|
0c7ce90980 | ||
|
|
4aba1bc7cb | ||
|
|
cf1ef1c819 | ||
|
|
855e376610 | ||
|
|
c6eeabce62 | ||
|
|
5e7b1ff0ba | ||
|
|
2b512b1315 | ||
|
|
dae2c224e5 | ||
|
|
fcda0abc21 | ||
|
|
c862d496e3 | ||
|
|
7968f83bf8 | ||
|
|
8cba1bad43 | ||
|
|
bc6365567f | ||
|
|
6c4e8adda1 | ||
|
|
c22ec9b074 | ||
|
|
3e753bcf97 | ||
|
|
6ba95de6e6 | ||
|
|
064d41588c | ||
|
|
582462a73f | ||
|
|
f58e7f04f1 | ||
|
|
bd951e19d3 | ||
|
|
91e99a2dbf | ||
|
|
a7d2beabe0 | ||
|
|
2f78d033ab | ||
|
|
cce74b29ad | ||
|
|
aa1c0a24e2 | ||
|
|
cf4d9b63c7 | ||
|
|
f0802c3035 | ||
|
|
86da6acf3f | ||
|
|
697bc882c7 | ||
|
|
8a0ffc940e | ||
|
|
63e947bf84 | ||
|
|
24329aa3d2 | ||
|
|
58bdaca252 | ||
|
|
6249049bcc | ||
|
|
70e89d9203 | ||
|
|
39f053eee4 | ||
|
|
4cfcb28c60 | ||
|
|
1027a2a77b | ||
|
|
5dd3ffd9ef | ||
|
|
2aa31ac911 | ||
|
|
3d49e0aabe | ||
|
|
8c425f62b6 | ||
|
|
9080697dc0 | ||
|
|
279bdf8c7e | ||
|
|
7d67ae2562 | ||
|
|
8922350379 | ||
|
|
df922b18a7 | ||
|
|
5d08565ff1 | ||
|
|
757a9b1e3e | ||
|
|
32bc096d9a | ||
|
|
5b52dcc7fe | ||
|
|
d871c378fe | ||
|
|
68bc60f6f4 | ||
|
|
47a3e71b01 | ||
|
|
dfa6fadf2d | ||
|
|
dffd8b5299 | ||
|
|
85f8dcef98 | ||
|
|
d3884c6eca | ||
|
|
98e2d8ad7a | ||
|
|
cde602d77d | ||
|
|
af168d2c57 | ||
|
|
a289ddf1fd | ||
|
|
323783b89e | ||
|
|
d423c08440 | ||
|
|
38fe983010 | ||
|
|
1b32dff6a4 | ||
|
|
189fb0d767 | ||
|
|
037995ab59 | ||
|
|
8dde9f19a4 | ||
|
|
8c77f3eddb | ||
|
|
e74bbe4044 | ||
|
|
9448ac1012 | ||
|
|
8b5bb28c94 | ||
|
|
caf1b5e9a9 | ||
|
|
fa7d586a97 | ||
|
|
7a3fd2150b | ||
|
|
0397183f2a | ||
|
|
751212db47 | ||
|
|
bc38385fe9 | ||
|
|
76f52846de | ||
|
|
771ac22d7f | ||
|
|
47b3bcf297 | ||
|
|
5e7dd321f0 | ||
|
|
489dac5488 | ||
|
|
b1ad643364 | ||
|
|
b40322dc2c | ||
|
|
6e2954d446 | ||
|
|
6b69651a21 | ||
|
|
cb1a5c90e6 | ||
|
|
6e1ab5f103 | ||
|
|
6a825cf3fd | ||
|
|
928bfd3d97 | ||
|
|
4e2457b05d | ||
|
|
98916b4404 | ||
|
|
48baf7812d | ||
|
|
1ee73eae37 | ||
|
|
18dd56e790 | ||
|
|
762293536f | ||
|
|
7c1519a0de | ||
|
|
e153efe9e4 | ||
|
|
70c64bf081 | ||
|
|
80071fbeaa | ||
|
|
0e653fdefa | ||
|
|
2418894dcb | ||
|
|
088e324b88 | ||
|
|
a3d0e2c588 | ||
|
|
c813b5a3c0 | ||
|
|
f71e4acf7e | ||
|
|
bdbb590dc4 | ||
|
|
07a04b069e | ||
|
|
d12b345fe8 | ||
|
|
9d57d0568c | ||
|
|
290b75de3f | ||
|
|
292623ab52 | ||
|
|
dfcbacd464 | ||
|
|
7fa16f2b70 | ||
|
|
372da19f30 | ||
|
|
0616f39e35 | ||
|
|
d51f17fdad | ||
|
|
3e7d40c4f6 | ||
|
|
d4d695fecf | ||
|
|
477a145712 | ||
|
|
2f912b0b95 | ||
|
|
83f4dba674 | ||
|
|
b05c2c4437 | ||
|
|
5b2edd0f7d | ||
|
|
aad71179a5 | ||
|
|
a2b7753cd8 | ||
|
|
2eac4d37b3 | ||
|
|
01ff72e775 | ||
|
|
6f3fb4dce4 | ||
|
|
9d214f9dab | ||
|
|
4b62a4eec7 | ||
|
|
3ff8bc68c3 | ||
|
|
f6e3b38e6a | ||
|
|
887b318e27 |
@@ -1,2 +1,3 @@
|
||||
SEARXNG_BASE_URL="http://127.0.0.1:8080"
|
||||
OPENAI_API_KEY='dont share this, not needed for local providers'
|
||||
OPENAI_API_KEY='xxxxx'
|
||||
DEEPSEEK_API_KEY='xxxxx'
|
||||
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [Fosowl ]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
*.wav
|
||||
config.ini
|
||||
*.DS_Store
|
||||
*.log
|
||||
*.tmp
|
||||
*.safetensors
|
||||
*.egg-info
|
||||
cookies.json
|
||||
test_agent.py
|
||||
config.ini
|
||||
.voices/
|
||||
experimental/
|
||||
.logs/
|
||||
.screenshots/*.png
|
||||
.screenshots/*.jpg
|
||||
conversations/
|
||||
agentic_env/*
|
||||
agentic_seek_env/*
|
||||
.env
|
||||
*/.env
|
||||
dsk/
|
||||
|
||||
### react ###
|
||||
.DS_*
|
||||
*.log
|
||||
logs
|
||||
**/*.backup.*
|
||||
**/*.back.*
|
||||
node_modules
|
||||
bower_components
|
||||
*.sublime*
|
||||
psd
|
||||
thumb
|
||||
sketch
|
||||
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: trufflehog
|
||||
name: TruffleHog
|
||||
description: Detect secrets in your data.
|
||||
entry: bash -c 'trufflehog git file://. --since-commit HEAD --results=verified,unknown --no-update'
|
||||
language: system
|
||||
stages: ["commit", "push"]
|
||||
@@ -1,90 +0,0 @@
|
||||
# Contributors guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8 or higher
|
||||
- Ollama installed (for local model execution)
|
||||
- Basic familiarity with Python and AI models
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
We welcome contributions in the following areas:
|
||||
|
||||
- Code Improvements: Optimize existing code, fix bugs, or add new features.
|
||||
- Documentation: Improve the README, write tutorials, or add inline comments.
|
||||
- Testing: Write unit tests, integration tests, or help with debugging.
|
||||
- New Features: Implement new tools, agents, or integrations.
|
||||
|
||||
## Steps to Contribute
|
||||
|
||||
Fork the project to your GitHub account.
|
||||
|
||||
Create a Branch:
|
||||
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
Make Your Changes.
|
||||
|
||||
Write your code, add documentation, or fix bugs.
|
||||
|
||||
Test Your Changes.
|
||||
|
||||
Ensure your changes work as expected and do not break existing functionality.
|
||||
|
||||
Push your changes to your fork and submit a pull request to the main branch of this repository. Provide a clear description of your changes and reference any related issues.
|
||||
|
||||
## Coding Philosophy
|
||||
|
||||
1. **Privacy First, Always Local**
|
||||
- All core functionality must be able to run 100% locally
|
||||
- Cloud services should only be optional alternatives, clearly defined with a warning message.
|
||||
- User data privacy is non-negotiable
|
||||
|
||||
2. **Agent-Based Architecture**
|
||||
- Each agent should have a clear, single responsibility
|
||||
- Agents should be modular and independently testable
|
||||
- New agents should solve specific use cases
|
||||
|
||||
3. **Tool-Based Extensibility**
|
||||
- Tools should be self-contained and follow the Tools base class
|
||||
- Each tool should do one thing well
|
||||
- Tools should provide clear feedback on success/failure
|
||||
|
||||
4. **User Experience**
|
||||
- Provide meaningful feedback for all operations
|
||||
- Support multiple languages (chinese, french, english for now)
|
||||
- Text to speech with short response.
|
||||
- Keep responses concise
|
||||
|
||||
5. **Code Quality**
|
||||
- Write clear, self-documenting code
|
||||
- Include type hints and docstrings
|
||||
- Follow existing patterns in the codebase
|
||||
- Add a if __name__ == "__main__" at the bottom of each class file for individual testing.
|
||||
- Ideally had automated tests.
|
||||
|
||||
6. **Error Handling**
|
||||
- Fail gracefully with meaningful messages
|
||||
- Include recovery mechanisms where possible
|
||||
- Log errors appropriately without exposing sensitive data
|
||||
|
||||
## Areas Needing Help
|
||||
|
||||
Here are some high-priority tasks and areas where we need contributions:
|
||||
|
||||
- Web Browsing: Implement autonomous web browsing capabilities for the assistant.
|
||||
- Multi-Agent System: Enhance the multi-agent functionality on the dev branch.
|
||||
- Memory & Recovery: Improve conversation compression.
|
||||
- New Tools: Add support for additional programming languages or APIs.
|
||||
- Testing: Write comprehensive tests for existing and new features.
|
||||
|
||||
|
||||
If you're unsure where to start, feel free to reach out by opening an issue or joining our community discussions.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
See CODE_OF_CONDUCT.md
|
||||
|
||||
**Thank You!**
|
||||
@@ -0,0 +1,47 @@
|
||||
FROM ubuntu:22.04
|
||||
# Warning: doesn't work yet, backend is run on host machine for now
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update -qq -y && \
|
||||
apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
gfortran \
|
||||
libportaudio2 \
|
||||
portaudio19-dev \
|
||||
ffmpeg \
|
||||
libavcodec-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
gnupg2 \
|
||||
wget \
|
||||
unzip \
|
||||
python3 \
|
||||
python3-pip \
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
libgtk-4-1 \
|
||||
libnss3 \
|
||||
xdg-utils \
|
||||
wget && \
|
||||
|
||||
RUN chmod +x /opt/chrome/chrome
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY api.py .
|
||||
COPY sources/ ./sources/
|
||||
COPY prompts/ ./prompts/
|
||||
COPY crx/ crx/
|
||||
COPY llm_router/ llm_router/
|
||||
COPY .env .
|
||||
COPY config.ini .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run the application
|
||||
CMD ["python3", "api.py"]
|
||||
@@ -1,54 +1,51 @@
|
||||
# AgenticSeek: Private, Local Manus Alternative.
|
||||
|
||||
# AgenticSeek: Manus-like AI powered by Deepseek R1 Agents.
|
||||
<p align="center">
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md)
|
||||
|
||||
**A fully local alternative to Manus AI**, a voice-enabled AI assistant that codes, explores your filesystem, browse the web and correct it's mistakes all without sending a byte of data to the cloud. Built with reasoning models like DeepSeek R1, this autonomous agent runs entirely on your hardware, keeping your data private.
|
||||
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
||||
|
||||
### Why AgenticSeek ?
|
||||
|
||||
* 🔒 Fully Local & Private - Everything runs on your machine — no cloud, no data sharing. Your files, conversations, and searches stay private.
|
||||
|
||||
* 🌐 Smart Web Browsing - AgenticSeek can browse the internet by itself — search, read, extract info, fill web form — all hands-free.
|
||||
|
||||
* 💻 Autonomous Coding Assistant - Need code? It can write, debug, and run programs in Python, C, Go, Java, and more — all without supervision.
|
||||
|
||||
* 🧠 Smart Agent Selection - You ask, it figures out the best agent for the job automatically. Like having a team of experts ready to help.
|
||||
|
||||
* 📋 Plans & Executes Complex Tasks - From trip planning to complex projects — it can split big tasks into steps and get things done using multiple AI agents.
|
||||
|
||||
* 🎙️ Voice-Enabled - Clean, fast, futuristic voice and speech to text allowing you to talk to it like it's your personal AI from a sci-fi movie
|
||||
|
||||
### **Demo**
|
||||
|
||||
> *Can you search for the agenticSeek project, learn what skills are required, then open the CV_candidates.zip and then tell me which match best the project*
|
||||
|
||||
https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316
|
||||
|
||||
Disclaimer: This demo, including all the files that appear (e.g: CV_candidates.zip), are entirely fictional. We are not a corporation, we seek open-source contributors not candidates.
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  
|
||||
> 🛠️ **Work in Progress** – Looking for contributors!
|
||||
|
||||

|
||||
## Installation
|
||||
|
||||
Make sure you have chrome driver, docker and python3.10 (or newer) installed.
|
||||
|
||||
For issues related to chrome driver, see the **Chromedriver** section.
|
||||
|
||||
## Features:
|
||||
|
||||
- **100% Local**: No cloud, runs on your hardware. Your data stays yours.
|
||||
|
||||
- **Voice interaction**: Voice-enabled natural interaction.
|
||||
|
||||
- **Filesystem interaction**: Use bash to navigate and manipulate your files effortlessly.
|
||||
|
||||
- **Code what you ask**: Can write, debug, and run code in Python, C, Golang and more languages on the way.
|
||||
|
||||
- **Autonomous**: If a command flops or code breaks, it retries and fixes it by itself.
|
||||
|
||||
- **Agent routing**: Automatically picks the right agent for the job.
|
||||
|
||||
- **Divide and Conquer**: For big tasks, spins up multiple agents to plan and execute.
|
||||
|
||||
- **Tool-Equipped**: From basic search to flight APIs and file exploration, every agent has it's own tools.
|
||||
|
||||
- **Memory**: Remembers what’s useful, your preferences and past sessions conversation.
|
||||
|
||||
- **Web Browsing**: Autonomous web navigation is underway.
|
||||
|
||||
|
||||
### Searching the web with agenticSeek :
|
||||
|
||||

|
||||
|
||||
*See media/examples for other use case screenshots.*
|
||||
|
||||
---
|
||||
|
||||
## **Installation**
|
||||
|
||||
### 1️⃣ **Clone the repository**
|
||||
### 1️⃣ **Clone the repository and setup**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2️ **Create a virtual env**
|
||||
@@ -61,198 +58,455 @@ source agentic_seek_env/bin/activate
|
||||
|
||||
### 3️⃣ **Install package**
|
||||
|
||||
**Automatic Installation:**
|
||||
**Automatic Installation (Recommanded):**
|
||||
|
||||
For Linux/Macos:
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
For windows:
|
||||
```sh
|
||||
./install.bat
|
||||
```
|
||||
|
||||
**Manually:**
|
||||
|
||||
First, you need to install these packages:
|
||||
|
||||
- *Linux*:
|
||||
|
||||
Updates package list (apt-get update).
|
||||
|
||||
Install these:
|
||||
alsa-utils, portaudio19-dev, python3-pyaudio, libgtk-3-dev, libnotify-dev, libgconf-2-4, libnss3, libxss1, selenium
|
||||
|
||||
Make sure to install docker + docker-compose if not already.
|
||||
|
||||
- *Macos*:
|
||||
|
||||
Update package list.
|
||||
Install chromedriver.
|
||||
Install portaudio.
|
||||
Install chromedriver and selenium.
|
||||
|
||||
- *Windows*:
|
||||
|
||||
Install pyreadline3, selenium portaudio, pyAudio and chromedriver
|
||||
|
||||
Then install pip requirements:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Run locally on your machine
|
||||
## Setup for running LLM locally on your machine
|
||||
|
||||
**We recommend using at least Deepseek 14B, smaller models struggle with tool use and forget quickly the context.**
|
||||
**We recommend using at the very least Deepseek 14B, smaller models will struggle with tasks especially for web browsing.**
|
||||
|
||||
### 1️⃣ **Download Models**
|
||||
|
||||
Make sure you have [Ollama](https://ollama.com/) installed.
|
||||
**Setup your local provider**
|
||||
|
||||
Download the `deepseek-r1:7b` model from [DeepSeek](https://deepseek.com/models)
|
||||
Start your local provider, for example with ollama:
|
||||
|
||||
```sh
|
||||
ollama pull deepseek-r1:7b
|
||||
```
|
||||
|
||||
### 2️ **Run the Assistant (Ollama)**
|
||||
|
||||
Start the ollama server
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
Change the config.ini file to set the provider_name to `ollama` and provider_model to `deepseek-r1:7b`
|
||||
See below for a list of local supported provider.
|
||||
|
||||
NOTE: `deepseek-r1:7b`is an example, use a bigger model if your hardware allow it.
|
||||
**Update the config.ini**
|
||||
|
||||
Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b`
|
||||
|
||||
NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:7b
|
||||
provider_name = ollama # or lm-studio, openai, etc..
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
```
|
||||
|
||||
start all services :
|
||||
**List of local providers**
|
||||
|
||||
```sh
|
||||
./start_services.sh
|
||||
```
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
||||
| openai | Yes | Use openai compatible API |
|
||||
|
||||
Run the assistant:
|
||||
|
||||
```sh
|
||||
python3 main.py
|
||||
```
|
||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
*See the **Known issues** section if you are having issues*
|
||||
|
||||
*See the **Run with an API** section if your hardware can't run deepseek locally*
|
||||
|
||||
*See the **Config** section for detailled config file explanation.*
|
||||
|
||||
---
|
||||
|
||||
## **Run the LLM on your own server**
|
||||
## Setup to run with an API
|
||||
|
||||
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server.
|
||||
Set the desired provider in the `config.ini`. See below for a list of API providers.
|
||||
|
||||
### 1️⃣ **Set up and start the server scripts**
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
Warning: Make sure there is not trailing space in the config.
|
||||
|
||||
Export your API key: `export <<PROVIDER>>_API_KEY="xxx"`
|
||||
|
||||
Example: export `TOGETHER_API_KEY="xxxxx"`
|
||||
|
||||
**List of API providers**
|
||||
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| openai | Depends | Use ChatGPT API |
|
||||
| deepseek-api | No | Deepseek API (non-private) |
|
||||
| huggingface| No | Hugging-Face API (non-private) |
|
||||
| togetherAI | No | Use together AI API (non-private) |
|
||||
| google | No | Use google gemini API (non-private) |
|
||||
|
||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
*See the **Known issues** section if you are having issues*
|
||||
|
||||
*See the **Config** section for detailled config file explanation.*
|
||||
|
||||
---
|
||||
|
||||
## Start services and Run
|
||||
|
||||
Activate your python env if needed.
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
Start required services. This will start all services from the docker-compose.yml, including:
|
||||
- searxng
|
||||
- redis (required by searxng)
|
||||
- frontend
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Window
|
||||
```
|
||||
|
||||
**Options 1:** Run with the CLI interface.
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**Options 2:** Run with the Web interface.
|
||||
|
||||
Start the backend.
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
Go to `http://localhost:3000/` and you should see the web interface.
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 cli.py` for CLI mode or `python3 api.py` then go to `localhost:3000` for web interface.
|
||||
|
||||
You can also use speech to text by setting `listen = True` in the config. Only for CLI mode.
|
||||
|
||||
To exit, simply say/type `goodbye`.
|
||||
|
||||
Here are some example usage:
|
||||
|
||||
### Coding/Bash
|
||||
|
||||
> *Make a snake game in python*
|
||||
|
||||
> *Show me how to multiply matrice in C*
|
||||
|
||||
> *Make a blackjack in golang*
|
||||
|
||||
### Web search
|
||||
|
||||
> *Do a web search to find cool tech startup in Japan working on cutting edge AI research*
|
||||
|
||||
> *Can you find on the internet who created AgenticSeek?*
|
||||
|
||||
> *Can you use a fuel calculator online to estimate the cost of a Nice - Milan trip*
|
||||
|
||||
### File system
|
||||
|
||||
> *Hey can you find where is contract.pdf i lost it*
|
||||
|
||||
> *Show me how much space I have left on my disk*
|
||||
|
||||
> *Can you follow the readme and install project at /home/path/project*
|
||||
|
||||
### Casual
|
||||
|
||||
> *Tell me about Rennes, France*
|
||||
|
||||
> *Should I pursue a phd ?*
|
||||
|
||||
> *What's the best workout routine ?*
|
||||
|
||||
|
||||
After you type your query, AgenticSeek will allocate the best agent for the task.
|
||||
|
||||
Because this is an early prototype, the agent routing system might not always allocate the right agent based on your query.
|
||||
|
||||
Therefore, you should be very explicit in what you want and how the AI might proceed for example if you want it to conduct a web search, do not say:
|
||||
|
||||
`Do you know some good countries for solo-travel?`
|
||||
|
||||
Instead, ask:
|
||||
|
||||
`Do a web search and find out which are the best country for solo-travel`
|
||||
|
||||
---
|
||||
|
||||
## **Setup to run the LLM on your own server**
|
||||
|
||||
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server using our custom llm server.
|
||||
|
||||
On your "server" that will run the AI model, get the ip address
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # local ip
|
||||
curl https://ipinfo.io/ip # public ip
|
||||
```
|
||||
|
||||
Note: For Windows or macOS, use ipconfig or ifconfig respectively to find the IP address.
|
||||
|
||||
Clone the repository and then, run the script `stream_llm.py` in `server/`
|
||||
Clone the repository and enter the `server/`folder.
|
||||
|
||||
|
||||
```sh
|
||||
python3 server_ollama.py
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
### 2️⃣ **Run it**
|
||||
Install server specific requirements:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
Run the server script.
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
You have the choice between using `ollama` and `llamacpp` as a LLM service.
|
||||
|
||||
|
||||
Now on your personal computer:
|
||||
|
||||
Clone the repository.
|
||||
|
||||
Change the `config.ini` file to set the `provider_name` to `server` and `provider_model` to `deepseek-r1:7b`.
|
||||
Change the `config.ini` file to set the `provider_name` to `server` and `provider_model` to `deepseek-r1:xxb`.
|
||||
Set the `provider_server_address` to the ip address of the machine that will run the model.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = x.x.x.x:5000
|
||||
provider_model = deepseek-r1:70b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
Run the assistant:
|
||||
|
||||
```sh
|
||||
./start_services.sh
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
## **Run with an API**
|
||||
|
||||
Clone the repository.
|
||||
|
||||
Set the desired provider in the `config.ini`
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt4-o
|
||||
provider_server_address = 127.0.0.1:5000 # can be set to anything, not used
|
||||
```
|
||||
|
||||
Run the assistant:
|
||||
|
||||
```sh
|
||||
./start_services.sh
|
||||
python3 main.py
|
||||
```
|
||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
---
|
||||
|
||||
## Speech to Text
|
||||
|
||||
Please note that currently speech to text only work in english.
|
||||
|
||||
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
When enabled, the speech-to-text feature listens for a trigger keyword, which is the agent's name, before it begins processing your input. You can customize the agent's name by updating the `agent_name` value in the *config.ini* file:
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
For optimal recognition, we recommend using a common English name like "John" or "Emma" as the agent name
|
||||
|
||||
Once you see the transcript start to appear, say the agent's name aloud to wake it up (e.g., "Friday").
|
||||
|
||||
Speak your query clearly.
|
||||
|
||||
End your request with a confirmation phrase to signal the system to proceed. Examples of confirmation phrases include:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Example config:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:32b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en zh
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**Explanation**:
|
||||
|
||||
- is_local -> Runs the agent locally (True) or on a remote server (False).
|
||||
|
||||
- provider_name -> The provider to use (one of: `ollama`, `server`, `lm-studio`, `deepseek-api`)
|
||||
|
||||
- provider_model -> The model used, e.g., deepseek-r1:32b.
|
||||
|
||||
- provider_server_address -> Server address, e.g., 127.0.0.1:11434 for local. Set to anything for non-local API.
|
||||
|
||||
- agent_name -> Name of the agent, e.g., Friday. Used as a trigger word for TTS.
|
||||
|
||||
- recover_last_session -> Restarts from last session (True) or not (False).
|
||||
|
||||
- save_session -> Saves session data (True) or not (False).
|
||||
|
||||
- speak -> Enables voice output (True) or not (False).
|
||||
|
||||
- listen -> listen to voice input (True) or not (False).
|
||||
|
||||
- work_dir -> Folder the AI will have access to. eg: /Users/user/Documents/.
|
||||
|
||||
- jarvis_personality -> Uses a JARVIS-like personality (True) or not (False). This simply change the prompt file.
|
||||
|
||||
- languages -> The list of supported language, needed for the llm router to work properly, avoid putting too many or too similar languages.
|
||||
|
||||
- headless_browser -> Runs browser without a visible window (True) or not (False).
|
||||
|
||||
- stealth_mode -> Make bot detector time harder. Only downside is you have to manually install the anticaptcha extension.
|
||||
|
||||
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
|
||||
|
||||
## Providers
|
||||
|
||||
The table below show the available providers:
|
||||
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| Ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||
| Server | Yes | Host the model on another machine, run your local machine |
|
||||
| OpenAI | No | Use ChatGPT API (non-private) |
|
||||
| Deepseek | No | Deepseek API (non-private) |
|
||||
| HuggingFace| No | Hugging-Face API (non-private) |
|
||||
|
||||
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||
| server | Yes | Host the model on another machine, run your local machine |
|
||||
| lm-studio | Yes | Run LLM locally with LM studio (`lm-studio`) |
|
||||
| openai | Depends | Use ChatGPT API (non-private) or openai compatible API |
|
||||
| deepseek-api | No | Deepseek API (non-private) |
|
||||
| huggingface| No | Hugging-Face API (non-private) |
|
||||
| togetherAI | No | Use together AI API (non-private) |
|
||||
| google | No | Use google gemini API (non-private) |
|
||||
|
||||
To select a provider change the config.ini:
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:32b
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
`is_local`: should be True for any locally running LLM, otherwise False.
|
||||
|
||||
`provider_name`: Select the provider to use by its name, see the provider list above.
|
||||
`provider_name`: Select the provider to use by it's name, see the provider list above.
|
||||
|
||||
`provider_model`: Set the model to use by the agent.
|
||||
|
||||
`provider_server_address`: can be set to anything if you are not using the server provider.
|
||||
|
||||
# Known issues
|
||||
|
||||
## Chromedriver Issues
|
||||
|
||||
**Known error #1:** *chromedriver mismatch*
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
This happen if there is a mismatch between your browser and chromedriver version.
|
||||
|
||||
You need to navigate to download the latest version:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
If you're using Chrome version 115 or newer go to:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
And download the chromedriver version matching your OS.
|
||||
|
||||

|
||||
|
||||
If this section is incomplete please raise an issue.
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: What hardware do I need?**
|
||||
|
||||
7B Model: GPU with 8GB VRAM.
|
||||
14B Model: 12GB GPU (e.g., RTX 3060).
|
||||
32B Model: 24GB+ VRAM.
|
||||
| Model Size | GPU | Comment |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB Vram | ⚠️ Not recommended. Performance is poor, frequent hallucinations, and planner agents will likely fail. |
|
||||
| 14B | 12 GB VRAM (e.g. RTX 3060) | ✅ Usable for simple tasks. May struggle with web browsing and planning tasks. |
|
||||
| 32B | 24+ GB VRAM (e.g. RTX 4090) | 🚀 Success with most tasks, might still struggle with task planning |
|
||||
| 70B+ | 48+ GB Vram (eg. mac studio) | 💪 Excellent. Recommended for advanced use cases. |
|
||||
|
||||
**Q: Why Deepseek R1 over other models?**
|
||||
|
||||
Deepseek R1 excels at reasoning and tool use for its size. We think it’s a solid fit for our needs other models work fine, but Deepseek is our primary pick.
|
||||
|
||||
**Q: I get an error running `main.py`. What do I do?**
|
||||
**Q: I get an error running `cli.py`. What do I do?**
|
||||
|
||||
Ensure Ollama is running (`ollama serve`), your `config.ini` matches your provider, and dependencies are installed. If none work feel free to raise an issue.
|
||||
|
||||
**Q: How to join the discord ?**
|
||||
|
||||
Ask in the Community section for an invite.
|
||||
Ensure local is running (`ollama serve`), your `config.ini` matches your provider, and dependencies are installed. If none work feel free to raise an issue.
|
||||
|
||||
**Q: Can it really run 100% locally?**
|
||||
|
||||
Yes with Ollama or Server providers, all speech to text, LLM and text to speech model run locally. Non-local options (OpenAI or others API) are optional.
|
||||
Yes with Ollama, lm-studio or server providers, all speech to text, LLM and text to speech model run locally. Non-local options (OpenAI or others API) are optional.
|
||||
|
||||
**Q: How come it is older than manus ?**
|
||||
**Q: Why should I use AgenticSeek when I have Manus?**
|
||||
|
||||
we started this a fun side project to make a fully local, Jarvis-like AI. However, with the rise of Manus, we saw the opportunity to redirected some tasks to make yet another alternative.
|
||||
|
||||
**Q: How is it better than manus ?**
|
||||
|
||||
It's not but we prioritizes local execution and privacy over cloud based approach. It’s a fun, accessible alternative!
|
||||
This started as Side-Project we did out of interest about AI agents. What’s special about it is that we want to use local model and avoid APIs.
|
||||
We draw inspiration from Jarvis and Friday (Iron man movies) to make it "cool" but for functionnality we take more inspiration from Manus, because that's what people want in the first place: a local manus alternative.
|
||||
Unlike Manus, AgenticSeek prioritizes independence from external systems, giving you more control, privacy and avoid api cost.
|
||||
|
||||
## Contribute
|
||||
|
||||
We’re looking for developers to improve AgenticSeek! Check out open issues or discussion.
|
||||
|
||||
## Authors:
|
||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
## Maintainers:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
|
||||
|
||||
[English](./README.md) | 中文 | [日本語](./README_JP.md)
|
||||
|
||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||
|
||||
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
> *在大阪和东京深入搜寻人工智慧新创公司,至少找到 5 家,然后储存在 research_japan.txt 档案中*
|
||||
|
||||
> *你可以用 C 语言制作俄罗斯方块游戏吗?*
|
||||
|
||||
> *我想设定一个新的专案档案索引,命名为 mark2。*
|
||||
|
||||
|
||||
|
||||
## Features:
|
||||
|
||||
- **100% 本机运行**: 本机运行,不使用云端服务,所以资料绝不会散布出去,我的东西还是我的!不会被当作其他服务的训练资料。
|
||||
|
||||
- **文件的交互系统**: 使用 bash 去浏览本机资料和操作本机系统。
|
||||
|
||||
- **自主 Coding**: AgenticSeek 可以自己运行、Debug、编译 Python、C、Golang 和各种语言。
|
||||
|
||||
- **代理助理**: 不同的工作由不同的助理去处理问题。AgenticSeek 会自己寻找最适合的助理去做相对应的工作。
|
||||
|
||||
- **规划**: 对于复杂的任务,AgenticSeek 会交办给不同的助理进行规划和执行。
|
||||
|
||||
- **自主学习**: 自动在网路上寻找资料。
|
||||
|
||||
- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。
|
||||
|
||||
---
|
||||
|
||||
## **安装**
|
||||
|
||||
确保已安装了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||
|
||||
有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。
|
||||
|
||||
### 1️⃣ **复制储存库与设置环境变数**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2️ **建立虚拟环境**
|
||||
|
||||
```sh
|
||||
python3 -m venv agentic_seek_env
|
||||
source agentic_seek_env/bin/activate
|
||||
# On Windows: agentic_seek_env\Scripts\activate
|
||||
```
|
||||
|
||||
### 3️⃣ **安装所需套件**
|
||||
|
||||
**自动安装:**
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
**手动安装:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
## 在本地机器上运行 AgenticSeek
|
||||
|
||||
**建议至少使用 Deepseek 14B 以上参数的模型,较小的模型难以使用助理功能并且很快就会忘记上下文之间的关系。**
|
||||
|
||||
**本地运行助手**
|
||||
|
||||
启动你的本地提供者,例如使用 ollama:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
请参阅下方支持的本地提供者列表。
|
||||
|
||||
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。
|
||||
|
||||
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # 或 lm-studio, openai 等
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
```
|
||||
|
||||
**本地提供者列表**
|
||||
|
||||
| 提供者 | 本地? | 描述 |
|
||||
|-------------|--------|-------------------------------------------------------|
|
||||
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地运行 LLM |
|
||||
| lm-studio | 是 | 使用 LM Studio 本地运行 LLM(将 `provider_name` 设置为 `lm-studio`)|
|
||||
| openai | 否 | 使用兼容的 API |
|
||||
|
||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
---
|
||||
|
||||
## **Run with an API (透过 API 执行)**
|
||||
|
||||
设定 `config.ini`。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
|
||||
警告:确保 `config.ini` 没有行尾空格。
|
||||
|
||||
如果使用基于本机的 openai-based api 则把 `is_local` 设定为 `True`。
|
||||
|
||||
同时更改你的 IP 为 openai-based api 的 IP。
|
||||
|
||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
---
|
||||
|
||||
## Start services and Run
|
||||
(启动服务并运行)
|
||||
|
||||
如果需要,请激活你的 Python 环境。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
|
||||
- searxng
|
||||
- redis(由 redis 提供支持)
|
||||
- 前端
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
```
|
||||
|
||||
**选项 1:** 使用 CLI 界面运行。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**选项 2:** 使用 Web 界面运行。
|
||||
|
||||
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
|
||||
|
||||
启动后端服务。
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
访问 `http://localhost:3000/`,你应该会看到 Web 界面。
|
||||
|
||||
请注意,目前 Web 界面不支持消息流式传输。
|
||||
|
||||
|
||||
*如果你不知道如何开始,请参阅 **Usage** 部分*
|
||||
|
||||
---
|
||||
|
||||
## Usage (使用方法)
|
||||
|
||||
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项。
|
||||
languages = en zh
|
||||
更多信息请参阅 Config 部分
|
||||
|
||||
确定所有的核心档案都启用了,也就是执行过这条命令 `./start_services.sh` 然后你就可以使用 `python3 cli.py` 来启动 AgenticSeek 了!
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
当你看到执行后显示 `>>> `
|
||||
这表示一切运作正常,AgenticSeek 正在等待你给他任何指令。
|
||||
你也可以透过设定 `config.ini` 内的 `listen = True` 来启用语音转文字。
|
||||
|
||||
要退出时,只要和他说 `goodbye` 就可以退出!
|
||||
|
||||
以下是一些用法:
|
||||
|
||||
### Coding/Bash
|
||||
|
||||
> *在 Golang 中帮助我进行矩阵乘法*
|
||||
|
||||
> *使用 nmap 扫描我的网路,找出是否有任何可疑装置连接*
|
||||
|
||||
> *用 Python 制作一个贪食蛇游戏*
|
||||
|
||||
### 网路搜寻
|
||||
|
||||
> *进行网路搜寻,找出日本从事尖端人工智慧研究的酷炫科技新创公司*
|
||||
|
||||
> *你能在网路上找到谁创造了 AgenticSeek 吗?*
|
||||
|
||||
> *你能在哪个网站上找到便宜的 RTX 4090 吗?*
|
||||
|
||||
### 档案浏览与搜寻
|
||||
|
||||
> *嘿,你能找到我遗失的 million_dollars_contract.pdf 在哪里吗?*
|
||||
|
||||
> *告诉我我的磁碟还剩下多少空间*
|
||||
|
||||
> *寻找并阅读 README.md,并按照安装说明进行操作*
|
||||
|
||||
### 日常聊天
|
||||
|
||||
> *告诉我关于法国的事*
|
||||
|
||||
> *人生的意义是什么?*
|
||||
|
||||
> *我应该在锻炼前还是锻炼后服用肌酸?*
|
||||
|
||||
|
||||
当你把指令送出后,AgenticSeek 会自动调用最能提供帮助的助理,去完成你交办的工作和指令。
|
||||
|
||||
但也有可能出现怪怪的情况,或是你要找飞机机票,他跑去教你如何一步步做出一台飞机(开玩笑的,但真的可能出现),因为这是一个早期专案,我们会努力教导他、完善他的!
|
||||
|
||||
所以我们希望你在使用时,能明确地表明你希望他要怎么做,下面给你一个范例!
|
||||
|
||||
你该说:
|
||||
- 进行网络搜索,找出哪些国家最适合独自旅行
|
||||
|
||||
|
||||
而不是说:
|
||||
- 你知道哪些国家适合独自旅行?
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **在本地执行属于你的 LLM 伺服器**
|
||||
|
||||
如果你有一台功能强大的电脑或伺服器,但你想透过笔记型电脑使用它,那么你可以选择在远端伺服器上执行 LLM。
|
||||
|
||||
### 1️⃣ **设定并启动伺服器脚本**
|
||||
|
||||
在运行 AI 模型的「伺服器」上,取得 IP 位址
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1
|
||||
```
|
||||
|
||||
注意:请在 Windows 或 MacOS,分别使用 `ipconfig` 与 `ifconfig` 来寻找 IP 位址。
|
||||
|
||||
**如果你希望使用基于 Openai 的服务,请按照 *透过 API 执行* 部分进行。**
|
||||
|
||||
复制储存库并且进入 `server/` 资料夹。
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
安装伺服器所需的套件:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
执行伺服器脚本。
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
您可以选择使用 `ollama` 或 `llamacpp` 作为 LLM 的服务框架。
|
||||
|
||||
### 2️⃣ **执行**
|
||||
|
||||
在你的电脑上:
|
||||
|
||||
- 更改 `config.ini`
|
||||
- `provider_name = server`
|
||||
- `provider_model = deepseek-r1:14b`
|
||||
- `provider_server_address = {你执行模型的电脑的 IP 位址}`
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 语音转文字
|
||||
|
||||
请注意,目前语音转文字功能仅支持英语。
|
||||
|
||||
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
启用后 AgenticSeek 会聆听你是否呼唤他,他才会开始听你说的话,你可以在 *config.ini* 内去设定,要怎么叫他。
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
为了获得比较好的结果,我们建议使用常见的英文名称(如 “John” 或 “Emma”)作为他的名字。
|
||||
|
||||
当你看到程式开始执行时,请大声说出他的名字,就可以唤醒 AgenticSeek 去聆听!(如:Friday)
|
||||
|
||||
清楚说出你的需求。
|
||||
|
||||
用确认短句结束你说的话,以通知 AgenticSeek 继续。确认短句的范例包括:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Config 范例:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:1.5b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en zh
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- is_local
|
||||
- True:在本地运行。
|
||||
- False:在远端伺服器运行。
|
||||
- provider_name
|
||||
- 框架类型
|
||||
- `ollama`, `server`, `lm-studio`, `deepseek-api`
|
||||
- provider_model
|
||||
- 运行的模型
|
||||
- `deepseek-r1:1.5b`, `deepseek-r1:14b`
|
||||
- provider_server_address
|
||||
- 伺服器 IP
|
||||
- `127.0.0.1:11434`
|
||||
- agent_name
|
||||
- AgenticSeek 的名字,用作TTS的触发单词。
|
||||
- `Friday`
|
||||
- recover_last_session
|
||||
- True:从上个对话继续。
|
||||
- False:重启对话。
|
||||
- save_session
|
||||
- True:储存对话纪录。
|
||||
- False:不保存。
|
||||
- speak
|
||||
- True:启用语音输出。
|
||||
- False:关闭语音输出。
|
||||
- listen
|
||||
- True:启用语音输入。
|
||||
- False:关闭语音输入。
|
||||
- work_dir
|
||||
- AgenticSeek 拥有能存取与交互的工作目录。
|
||||
- jarvis_personality
|
||||
> 就是那个钢铁人的 JARVIS
|
||||
- True:启用 JARVIS 个性。
|
||||
- False:关闭 JARVIS 个性。
|
||||
- headless_browser
|
||||
- True:前景浏览器。(很酷,推荐使用他 XD)
|
||||
- False:背景执行浏览器。
|
||||
- stealth_mode
|
||||
- 隐私模式,但需要你自己安装反爬虫扩充功能。
|
||||
- languages
|
||||
- 支持的语言列表。用于代理路由系统。语言列表越长,下载的模型越多。
|
||||
|
||||
## 框架
|
||||
|
||||
下表显示了可用的框架:
|
||||
|
||||
| 框架 | 本地? | 描述|
|
||||
|-|-|-|
|
||||
| ollama | 可 | 使用 ollama 框架去执行本地模型 |
|
||||
| server | 可 | 本地伺服器执行模型远端调用 |
|
||||
| lm-studio | 可 | 使用 LM Studio 在本地运行 LLM(设定provider_name为lm-studio)|
|
||||
| openai | 不可 | 使用 ChatGPT API(无法保证隐私)|
|
||||
| deepseek-api | 不可 | 使用 Deepseek API (无法保证隐私)|
|
||||
| huggingface | 不可 | 使用 Hugging-Face API (无法保证隐私)|
|
||||
|
||||
若要选择框架,请变更 `config.ini` 文件:
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
`is_local`: 对于任何本地运行的 LLM 都应该为 True,否则为 False。
|
||||
|
||||
`provider_name`: 透过名称选择要使用的框架,请参阅上面的框架清单。
|
||||
|
||||
`provider_model`: 设定 AgenticSeek 使用的模型。
|
||||
|
||||
`provider_server_address`: 如果不使用云端 API,则可以将其设定为任何内容。
|
||||
|
||||
# Known issues (已知问题)
|
||||
|
||||
## Chromedriver Issues
|
||||
|
||||
**已知问题 #1:** *chromedriver mismatch*
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
如果你的浏览器和 chromedriver 版本不一样,就会发生这种情况。
|
||||
|
||||
你可以透过以下连结下载最新版本:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
如果您使用的是 Chrome 版本 115 或更新版本,请前往:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
下载与你的作业系统相符的 chromedriver 版本。
|
||||
|
||||

|
||||
|
||||
如果有其他问题,请提供尽量详细的叙述到 Issues 上,尽可能包含当前环境和问题是怎么发生的。
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: 我需要什麼硬體?**
|
||||
|
||||
| 模型大小 | GPU | 備註 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB Vram | ⚠️ 不推薦。性能較差,經常出現幻覺,規劃代理可能會失敗。 |
|
||||
| 14B | 12 GB VRAM (例如 RTX 3060) | ✅ 適用於簡單任務。可能在網頁瀏覽和規劃任務上表現不佳。 |
|
||||
| 32B | 24+ GB VRAM (例如 RTX 4090) | 🚀 大多數任務成功,但可能仍在任務規劃上有困難。 |
|
||||
| 70B+ | 48+ GB Vram (例如 mac studio) | 💪 表現優異。建議用於高級使用情境。 |
|
||||
|
||||
**Q:为什么选择 Deepseek R1 而不是其他模型?**
|
||||
|
||||
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
|
||||
|
||||
**Q:我在执行时 `cli.py` 时出现错误。我该怎么办?**
|
||||
|
||||
1. 确保 Ollama 正在运行(ollama serve)
|
||||
2. 你 `config.ini` 内 `provider_name` 的框架选择正确。
|
||||
3. 依赖套件已安装
|
||||
4. 如果均无效,请随时提出 Issues,同样尽可能包含当前环境和问题是怎么发生的。
|
||||
|
||||
**Q:它真的是 100% 本地运行吗?**
|
||||
|
||||
是的,透过 Ollama 或其他框架,所有语音转文字、LLM 和文字转语音模型都在本地运行。
|
||||
*但你能选择非本地执行(OpenAI 或其他 API),同样也是可以的*
|
||||
|
||||
|
||||
**Q:我有 Manus 为甚么还要用 AgenticSeek?**
|
||||
|
||||
这是我们因为兴趣做的一个小 Side-Project,他特别的点在于是一个全部本地化的模型,而且可以像钢铁人里面一样与 `Jarvis` 对话,听起来就超级酷的吧!随着 Manus 的进化,我们也相应的加入更多功能!
|
||||
|
||||
**Q:它比 Manus 好在哪里?**
|
||||
|
||||
不不不,AgenticSeek 和 Manus 是不同取向的东西,我们优先考虑的是本地执行和隐私,而不是基于云端。这是一个与 Manus 相比起来更有趣且易使用的方案!
|
||||
|
||||
**Q: 是否支持中文以外的语言?**
|
||||
|
||||
DeepSeek R1 天生会说中文
|
||||
|
||||
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
||||
|
||||
如果不设置中文?后果可能是:你让它写代码,结果跳出来个"医生代理"(虽然我们根本没有这个代理... 但系统会一脸懵圈!)
|
||||
|
||||
实际上会下载一个小型翻译模型来协助任务分配
|
||||
|
||||
## 贡献
|
||||
|
||||
我们正在寻找开发者来改善 AgenticSeek!你可以在 Issues 查看未解决的问题或和我们讨论更酷的新功能!
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||
|
||||
## 作者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
@@ -0,0 +1,516 @@
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
|
||||
|
||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||
|
||||
|
||||
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
> *在大阪和東京深入搜尋人工智慧新創公司,至少找到 5 家,然後儲存在 research_japan.txt 檔案中*
|
||||
|
||||
> *你可以用 C 語言製作俄羅斯方塊遊戲嗎?*
|
||||
|
||||
> *我想設定一個新的專案檔案索引,命名為 mark2。*
|
||||
|
||||
|
||||
|
||||
## Features:
|
||||
|
||||
- **100% 本機運行**: 本機運行,不使用雲端服務,所以資料絕不會散布出去,我的東西還是我的!不會被當作其他服務的訓練資料。
|
||||
|
||||
- **文件的交互系統**: 使用 bash 去瀏覽本機資料和操作本機系統。
|
||||
|
||||
- **自主 Coding**: AgenticSeek 可以自己運行、Debug、編譯 Python、C、Golang 和各種語言。
|
||||
|
||||
- **代理助理**: 不同的工作由不同的助理去處理問題。AgenticSeek 會自己尋找最適合的助理去做相對應的工作。
|
||||
|
||||
- **規劃**: 對於複雜的任務,AgenticSeek 會交辦給不同的助理進行規劃和執行。
|
||||
|
||||
- **自主學習**: 自動在網路上尋找資料。
|
||||
|
||||
- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。
|
||||
|
||||
---
|
||||
|
||||
## **安裝**
|
||||
|
||||
確保已安裝了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||
|
||||
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
|
||||
|
||||
### 1️⃣ **複製儲存庫與設置環境變數**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2️ **建立虛擬環境**
|
||||
|
||||
```sh
|
||||
python3 -m venv agentic_seek_env
|
||||
source agentic_seek_env/bin/activate
|
||||
# On Windows: agentic_seek_env\Scripts\activate
|
||||
```
|
||||
|
||||
### 3️⃣ **安裝所需套件**
|
||||
|
||||
**自動安裝:**
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
**手動安裝:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
## 在本地機器上運行 AgenticSeek
|
||||
|
||||
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
|
||||
|
||||
**本地运行助手**
|
||||
|
||||
启动你的本地提供者,例如使用 ollama:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
请参阅下方支持的本地提供者列表。
|
||||
|
||||
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。
|
||||
|
||||
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # 或 lm-studio, openai 等
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
```
|
||||
|
||||
**本地提供者列表**
|
||||
|
||||
| 提供者 | 本地? | 描述 |
|
||||
|-------------|--------|-------------------------------------------------------|
|
||||
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地运行 LLM |
|
||||
| lm-studio | 是 | 使用 LM Studio 本地运行 LLM(将 `provider_name` 设置为 `lm-studio`)|
|
||||
| openai | 否 | 使用兼容的 API |
|
||||
|
||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
---
|
||||
|
||||
## **Run with an API (透過 API 執行)**
|
||||
|
||||
設定 `config.ini`。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
|
||||
警告:確保 `config.ini` 沒有行尾空格。
|
||||
|
||||
如果使用基於本機的 openai-based api 則把 `is_local` 設定為 `True`。
|
||||
|
||||
同時更改你的 IP 為 openai-based api 的 IP。
|
||||
|
||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
---
|
||||
|
||||
## Start services and Run
|
||||
(启动服务并运行)
|
||||
|
||||
如果需要,请激活你的 Python 环境。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
|
||||
- searxng
|
||||
- redis(由 redis 提供支持)
|
||||
- 前端
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
```
|
||||
|
||||
**选项 1:** 使用 CLI 界面运行。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**选项 2:** 使用 Web 界面运行。
|
||||
|
||||
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
|
||||
|
||||
启动后端服务。
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
访问 `http://localhost:3000/`,你应该会看到 Web 界面。
|
||||
|
||||
请注意,目前 Web 界面不支持消息流式传输。
|
||||
|
||||
|
||||
*如果你不知道如何開始,請參閱 **Usage** 部分*
|
||||
|
||||
---
|
||||
|
||||
## Usage (使用方法)
|
||||
|
||||
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项。
|
||||
languages = en zh
|
||||
更多信息请参阅 Config 部分
|
||||
|
||||
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
當你看到執行後顯示 `>>> `
|
||||
這表示一切運作正常,AgenticSeek 正在等待你給他任何指令。
|
||||
你也可以透過設定 `config.ini` 內的 `listen = True` 來啟用語音轉文字。
|
||||
|
||||
要退出時,只要和他說 `goodbye` 就可以退出!
|
||||
|
||||
以下是一些用法:
|
||||
|
||||
### Coding/Bash
|
||||
|
||||
> *在 Golang 中幫助我進行矩陣乘法*
|
||||
|
||||
> *使用 nmap 掃描我的網路,找出是否有任何可疑裝置連接*
|
||||
|
||||
> *用 Python 製作一個貪食蛇遊戲*
|
||||
|
||||
### 網路搜尋
|
||||
|
||||
> *進行網路搜尋,找出日本從事尖端人工智慧研究的酷炫科技新創公司*
|
||||
|
||||
> *你能在網路上找到誰創造了 AgenticSeek 嗎?*
|
||||
|
||||
> *你能在哪個網站上找到便宜的 RTX 4090 嗎?*
|
||||
|
||||
### 檔案瀏覽與搜尋
|
||||
|
||||
> *嘿,你能找到我遺失的 million_dollars_contract.pdf 在哪裡嗎?*
|
||||
|
||||
> *告訴我我的磁碟還剩下多少空間*
|
||||
|
||||
> *尋找並閱讀 README.md,並按照安裝說明進行操作*
|
||||
|
||||
### 日常聊天
|
||||
|
||||
> *告訴我關於法國的事*
|
||||
|
||||
> *人生的意義是什麼?*
|
||||
|
||||
> *我應該在鍛鍊前還是鍛鍊後服用肌酸?*
|
||||
|
||||
|
||||
當你把指令送出後,AgenticSeek 會自動調用最能提供幫助的助理,去完成你交辦的工作和指令。
|
||||
|
||||
但也有可能出現怪怪的情況,或是你要找飛機機票,他跑去教你如何一步步做出一台飛機(開玩笑的,但真的可能出現),因為這是一個早期專案,我們會努力教導他、完善他的!
|
||||
|
||||
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
|
||||
|
||||
你該說:
|
||||
- 进行网络搜索,找出哪些国家最适合独自旅行
|
||||
|
||||
|
||||
而不是說:
|
||||
- 你知道哪些国家适合独自旅行?
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **在本地執行屬於你的 LLM 伺服器**
|
||||
|
||||
如果你有一台功能強大的電腦或伺服器,但你想透過筆記型電腦使用它,那麼你可以選擇在遠端伺服器上執行 LLM。
|
||||
|
||||
### 1️⃣ **設定並啟動伺服器腳本**
|
||||
|
||||
在運行 AI 模型的「伺服器」上,取得 IP 位址
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1
|
||||
```
|
||||
|
||||
注意:請在 Windows 或 MacOS,分別使用 `ipconfig` 與 `ifconfig` 來尋找 IP 位址。
|
||||
|
||||
**如果你希望使用基於 Openai 的服務,請按照 *透過 API 執行* 部分進行。**
|
||||
|
||||
複製儲存庫並且進入 `server/` 資料夾。
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
安裝伺服器所需的套件:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
執行伺服器腳本。
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
您可以選擇使用 `ollama` 或 `llamacpp` 作為 LLM 的服務框架。
|
||||
|
||||
### 2️⃣ **執行**
|
||||
|
||||
在你的電腦上:
|
||||
|
||||
- 更改 `config.ini`
|
||||
- `provider_name = server`
|
||||
- `provider_model = deepseek-r1:14b`
|
||||
- `provider_server_address = {你執行模型的電腦的 IP 位址}`
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 語音轉文字
|
||||
|
||||
请注意,目前语音转文字功能仅支持英语。
|
||||
|
||||
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
啟用後 AgenticSeek 會聆聽你是否呼喚他,他才會開始聽你說的話,你可以在 *config.ini* 內去設定,要怎麼叫他。
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
為了獲得比較好的結果,我們建議使用常見的英文名稱(如 “John” 或 “Emma”)作為他的名字。
|
||||
|
||||
當你看到程式開始執行時,請大聲說出他的名字,就可以喚醒 AgenticSeek 去聆聽!(如:Friday)
|
||||
|
||||
清楚說出你的需求。
|
||||
|
||||
用確認短句結束你說的話,以通知 AgenticSeek 繼續。確認短句的範例包括:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## Config
|
||||
|
||||
Config 範例:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:1.5b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en zh
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**說明**:
|
||||
- is_local
|
||||
- True:在本地運行。
|
||||
- False:在遠端伺服器運行。
|
||||
- provider_name
|
||||
- 框架類型
|
||||
- `ollama`, `server`, `lm-studio`, `deepseek-api`
|
||||
- provider_model
|
||||
- 運行的模型
|
||||
- `deepseek-r1:1.5b`, `deepseek-r1:14b`
|
||||
- provider_server_address
|
||||
- 伺服器 IP
|
||||
- `127.0.0.1:11434`
|
||||
- agent_name
|
||||
- AgenticSeek 的名字,用作TTS的觸發單詞。
|
||||
- `Friday`
|
||||
- recover_last_session
|
||||
- True:從上個對話繼續。
|
||||
- False:重啟對話。
|
||||
- save_session
|
||||
- True:儲存對話紀錄。
|
||||
- False:不保存。
|
||||
- speak
|
||||
- True:啟用語音輸出。
|
||||
- False:關閉語音輸出。
|
||||
- listen
|
||||
- True:啟用語音輸入。
|
||||
- False:關閉語音輸入。
|
||||
- work_dir
|
||||
- AgenticSeek 擁有能存取與交互的工作目錄。
|
||||
- jarvis_personality
|
||||
> 就是那個鋼鐵人的 JARVIS
|
||||
- True:啟用 JARVIS 個性。
|
||||
- False:關閉 JARVIS 個性。
|
||||
- headless_browser
|
||||
- True:前景瀏覽器。(很酷,推薦使用他 XD)
|
||||
- False:背景執行瀏覽器。
|
||||
- stealth_mode
|
||||
- 隱私模式,但需要你自己安裝反爬蟲擴充功能。
|
||||
- languages
|
||||
- 支持的语言列表。用于代理路由系统。语言列表越长,下载的模型越多。
|
||||
|
||||
## 框架
|
||||
|
||||
下表顯示了可用的框架:
|
||||
|
||||
| 框架 | 本地? | 描述|
|
||||
|-|-|-|
|
||||
| ollama | 可 | 使用 ollama 框架去執行本地模型 |
|
||||
| server | 可 | 本地伺服器執行模型遠端調用 |
|
||||
| lm-studio | 可 | 使用 LM Studio 在本地運行 LLM(設定provider_name為lm-studio)|
|
||||
| openai | 不可 | 使用 ChatGPT API(無法保證隱私)|
|
||||
| deepseek-api | 不可 | 使用 Deepseek API (無法保證隱私)|
|
||||
| huggingface | 不可 | 使用 Hugging-Face API (無法保證隱私)|
|
||||
|
||||
若要選擇框架,請變更 `config.ini` 文件:
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
`is_local`: 對於任何本地運行的 LLM 都應該為 True,否則為 False。
|
||||
|
||||
`provider_name`: 透過名稱選擇要使用的框架,請參閱上面的框架清單。
|
||||
|
||||
`provider_model`: 設定 AgenticSeek 使用的模型。
|
||||
|
||||
`provider_server_address`: 如果不使用雲端 API,則可以將其設定為任何內容。
|
||||
|
||||
# Known issues (已知問題)
|
||||
|
||||
## Chromedriver Issues
|
||||
|
||||
**已知問題 #1:** *chromedriver mismatch*
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
如果你的瀏覽器和 chromedriver 版本不一樣,就會發生這種情況。
|
||||
|
||||
你可以透過以下連結下載最新版本:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
如果您使用的是 Chrome 版本 115 或更新版本,請前往:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
下載與你的作業系統相符的 chromedriver 版本。
|
||||
|
||||

|
||||
|
||||
如果有其他問題,請提供盡量詳細的敘述到 Issues 上,盡可能包含當前環境和問題是怎麼發生的。
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: 我需要什麼硬體?**
|
||||
|
||||
| 模型大小 | GPU | 備註 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB Vram | ⚠️ 不推薦。性能較差,經常出現幻覺,規劃代理可能會失敗。 |
|
||||
| 14B | 12 GB VRAM (例如 RTX 3060) | ✅ 適用於簡單任務。可能在網頁瀏覽和規劃任務上表現不佳。 |
|
||||
| 32B | 24+ GB VRAM (例如 RTX 4090) | 🚀 大多數任務成功,但可能仍在任務規劃上有困難。 |
|
||||
| 70B+ | 48+ GB Vram (例如 mac studio) | 💪 表現優異。建議用於高級使用情境。 |
|
||||
|
||||
**Q:為什麼選擇 Deepseek R1 而不是其他模型?**
|
||||
|
||||
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
|
||||
|
||||
**Q:我在執行時 `cli.py` 時出現錯誤。我該怎麼辦?**
|
||||
|
||||
1. 確保 Ollama 正在運行(ollama serve)
|
||||
2. 你 `config.ini` 內 `provider_name` 的框架選擇正確。
|
||||
3. 依賴套件已安裝
|
||||
4. 如果均無效,請隨時提出 Issues,同樣盡可能包含當前環境和問題是怎麼發生的。
|
||||
|
||||
**Q:它真的是 100% 本地運行嗎?**
|
||||
|
||||
是的,透過 Ollama 或其他框架,所有語音轉文字、LLM 和文字轉語音模型都在本地運行。
|
||||
*但你能選擇非本地執行(OpenAI 或其他 API),同樣也是可以的*
|
||||
|
||||
|
||||
**Q:我有 Manus 為甚麼還要用 AgenticSeek?**
|
||||
|
||||
這是我們因為興趣做的一個小 Side-Project,他特別的點在於是一個全部本地化的模型,而且可以像鋼鐵人裡面一樣與 `Jarvis` 對話,聽起來就超級酷的吧!隨著 Manus 的進化,我們也相應的加入更多功能!
|
||||
|
||||
**Q:它比 Manus 好在哪裡?**
|
||||
|
||||
不不不,AgenticSeek 和 Manus 是不同取向的東西,我們優先考慮的是本地執行和隱私,而不是基於雲端。這是一個與 Manus 相比起來更有趣且易使用的方案!
|
||||
|
||||
**Q: 是否支持中文以外的语言?**
|
||||
|
||||
DeepSeek R1 天生会说中文
|
||||
|
||||
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
||||
|
||||
如果不设置中文?后果可能是:你让它写代码,结果跳出来个"医生代理"(虽然我们根本没有这个代理... 但系统会一脸懵圈!)
|
||||
|
||||
实际上会下载一个小型翻译模型来协助任务分配
|
||||
|
||||
## 貢獻
|
||||
|
||||
我們正在尋找開發者來改善 AgenticSeek!你可以在 Issues 查看未解決的問題或和我們討論更酷的新功能!
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||
|
||||
## 作者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
@@ -0,0 +1,444 @@
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
[English](./README.md) | [繁體中文](./README_CHT.md) | [日本語](./README_JP.md) | Français
|
||||
|
||||
# AgenticSeek: Une IA comme Manus mais à base d'agents DeepSeek R1 fonctionnant en local.
|
||||
|
||||
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **En cours de développement** – On cherche activement des contributeurs!
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
> *Recherche sur le web des activités à faire à Paris*
|
||||
|
||||
> *Code le jeu snake en python*
|
||||
|
||||
> *J'aimerais que tu trouve une api météo et que tu me code une application qui affiche la météo à Toulouse*
|
||||
|
||||
|
||||
|
||||
|
||||
## Fonctionnalités:
|
||||
|
||||
- **100% Local**: Fonctionne en local sur votre PC. Vos données restent les vôtres.
|
||||
|
||||
- **Accès à vos Fichiers**: Utilise bash pour naviguer et manipuler vos fichiers.
|
||||
|
||||
- **Codage semi-autonome**: Peut écrire, déboguer et exécuter du code en Python, C, Golang et d'autres langages à venir.
|
||||
|
||||
- **Routage d'Agent**: Sélectionne automatiquement l’agent approprié pour la tâche.
|
||||
|
||||
- **Planification**: Pour les taches complexe utilise plusieurs agents.
|
||||
|
||||
- **Navigation Web Autonome**: Navigation web autonome.
|
||||
|
||||
- **Memoire efficace**: Gestion efficace de la mémoire et des sessions.
|
||||
|
||||
---
|
||||
|
||||
## **Installation**
|
||||
|
||||
Assurez-vous d’avoir installé le pilote Chrome, Docker et Python 3.10 (ou une version plus récente).
|
||||
|
||||
Pour les problèmes liés au pilote Chrome, consultez la section Chromedriver.
|
||||
|
||||
### 1️⃣ Cloner le repo et configurer
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2 **Créer un environnement virtuel**
|
||||
|
||||
```sh
|
||||
python3 -m venv agentic_seek_env
|
||||
source agentic_seek_env/bin/activate
|
||||
# Sur Windows: agentic_seek_env\Scripts\activate
|
||||
```
|
||||
|
||||
### 3️⃣ **Installation**
|
||||
|
||||
**Automatique:**
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
**Manuel:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
|
||||
## Faire fonctionner sur votre machine
|
||||
|
||||
**Nous recommandons d’utiliser au minimum DeepSeek 14B, les modèles plus petits ont du mal avec l’utilisation des outils et oublient rapidement le contexte.**
|
||||
|
||||
Lancer votre provider local, par exemple avec ollama:
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
Voyez la section **Provider** pour la liste de provideurs disponible.
|
||||
|
||||
Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # ou lm-studio, openai, etc...
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
```
|
||||
|
||||
**Liste des provideurs locaux**
|
||||
|
||||
| Fournisseur | Local ? | Description |
|
||||
|-------------|---------|-----------------------------------------------------------|
|
||||
| ollama | Oui | Exécutez des LLM localement avec facilité en utilisant ollama comme fournisseur LLM |
|
||||
| lm-studio | Oui | Exécutez un LLM localement avec LM studio (définissez `provider_name` sur `lm-studio`) |
|
||||
| openai | Oui | Utilisez une API local compatible avec openai |
|
||||
|
||||
|
||||
### **Démarrer les services & Exécuter**
|
||||
|
||||
Activez votre environnement Python si nécessaire.
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
Démarrez les services requis. Cela lancera tous les services définis dans le fichier docker-compose.yml, y compris :
|
||||
- searxng
|
||||
- redis (nécessaire pour searxng)
|
||||
- frontend
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
```
|
||||
|
||||
**Option 1 :** Exécuter avec l'interface CLI.
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**Option 2 :** Exécuter avec l'interface Web.
|
||||
|
||||
Démarrez le backend.
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
Allez sur `http://localhost:3000/` et vous devriez voir l'interface web.
|
||||
|
||||
Veuillez noter que l'interface web ne diffuse pas les messages en continu pour le moment.
|
||||
|
||||
|
||||
Voyez la section **Utilisation** si vous ne comprenez pas comment l’utiliser
|
||||
|
||||
Voyez la section **Problèmes** connus si vous rencontrez des problèmes
|
||||
|
||||
Voyez la section **Exécuter avec une API** si votre matériel ne peut pas exécuter DeepSeek localement
|
||||
|
||||
Voyez la section **Configuration** pour une explication détaillée du fichier de configuration.
|
||||
|
||||
---
|
||||
|
||||
## Utilisation
|
||||
|
||||
Assurez-vous que les services sont en cours d’exécution avec ./start_services.sh et lancez AgenticSeek avec le CLI ou l'interface Web.
|
||||
|
||||
**CLI:**
|
||||
Vous verrez un prompt : ">>> "
|
||||
Cela indique qu’AgenticSeek attend que vous saisissiez des instructions.
|
||||
Vous pouvez également utiliser la reconnaissance vocale en définissant `listen = True` dans la configuration.
|
||||
Pour quitter, dites simplement `goodbye`.
|
||||
|
||||
**Interface:**
|
||||
|
||||
Assurez-vous d'avoir bien démarré le backend avec `python3 api.py`.
|
||||
Allez sur `localhost:3000` où vous verrez une interface web.
|
||||
Tapez simplement votre message et patientez.
|
||||
Si vous n'avez pas d'interface sur `localhost:3000`, c'est que vous n'avez pas démarré les services avec `start_services.sh`.
|
||||
|
||||
Voici quelques exemples d’utilisation :
|
||||
|
||||
### Programmation
|
||||
|
||||
> *Aide-moi avec la multiplication de matrices en Golang*
|
||||
|
||||
> *Initalize un nouveau project python, setup le readme, gitignore etc.. et fait un premier commit*
|
||||
|
||||
> *Fais un jeu snake en Python*
|
||||
|
||||
### Recherche web
|
||||
|
||||
> *Fais une recherche sur le web pour trouver des startups technologiques au Japon qui travaillent sur des recherches avancées en IA*
|
||||
|
||||
> *Peux-tu trouver sur internet qui a créé agenticSeek ?*
|
||||
|
||||
> *Peux-tu trouver sur quel site je peux acheter une RTX 4090 à bas prix ?*
|
||||
|
||||
### Fichier
|
||||
|
||||
> *Hé, peux-tu trouver où est contrat.pdf ? Je l’ai perdu*
|
||||
|
||||
> *Montre-moi combien d’espace il me reste sur mon disque*
|
||||
|
||||
> *Trouve et lis le fichier README.md et suis les instructions d’installation*
|
||||
|
||||
### Conversation
|
||||
|
||||
> *Parle-moi de la France*
|
||||
|
||||
> *Quel est le sens de la vie ?*
|
||||
|
||||
> *Donne moi une recette simple pour ce midi j'ai pas d'inspi*
|
||||
|
||||
Après avoir saisi votre requête, AgenticSeek attribuera le meilleur agent pour la tâche.
|
||||
|
||||
Le système de routage des agents peut parfois ne pas toujours attribuer le bon agent en fonction de votre requête.
|
||||
|
||||
Par conséquent, vous devez être assez explicite sur ce que vous voulez et sur la manière dont l’IA doit procéder. Par exemple, si vous voulez qu’elle effectue une recherche sur le web, ne dites pas :
|
||||
|
||||
Connait-tu de bons pays pour voyager seul ?
|
||||
|
||||
Dites plutôt :
|
||||
|
||||
Fait une recherche sur le web, quels sont les meilleurs pays pour voyager seul?
|
||||
|
||||
---
|
||||
|
||||
## **Exécuter le LLM sur votre propre serveur**
|
||||
|
||||
Si vous disposez d’un ordinateur puissant ou d’un serveur que vous voulez utiliser, mais que vous souhaitez y accéder depuis votre ordinateur portable, vous avez la possibilité d’exécuter le LLM sur un serveur distant.
|
||||
|
||||
### 1️⃣ **Configurer et démarrer les scripts du serveur**
|
||||
|
||||
Sur votre "serveur" qui exécutera le modèle IA, obtenez l’adresse IP
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1
|
||||
```
|
||||
|
||||
Remarque : Pour Windows ou macOS, utilisez respectivement ipconfig ou ifconfig pour trouver l’adresse IP.
|
||||
|
||||
Clonez le dépôt et entrez dans le dossier server/.
|
||||
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
Installez les dépendances spécifiques au serveur :
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
Exécutez le script du serveur.
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
Vous avez le choix entre utiliser ollama et llamacpp comme service LLM.
|
||||
|
||||
### 2️⃣ **Lancer**
|
||||
|
||||
Maintenant, sur votre ordinateur personnel :
|
||||
|
||||
Modifiez le fichier config.ini pour définir provider_name sur server et provider_model sur deepseek-r1:14b.
|
||||
|
||||
Définissez provider_server_address sur l’adresse IP de la machine qui exécutera le modèle.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||
|
||||
## **Exécuter avec une API externe**
|
||||
|
||||
AVERTISSEMENT : Assurez-vous qu’il n’y a pas d’espace en fin de ligne dans la configuration.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000 # n'importe pas
|
||||
```
|
||||
|
||||
**Liste de provideurs API**
|
||||
| Fournisseur | Local ? | Description |
|
||||
|--------------|---------|-----------------------------------------------------------|
|
||||
| openai | Non | Utilise l'API ChatGPT |
|
||||
| deepseek-api | Non | API Deepseek (non privé) |
|
||||
| huggingface | Non | API Hugging-Face (non privé) |
|
||||
| togetherAI | Non | Utilise l'API Together AI (non privé) |
|
||||
| google | Non | Utilise l'API Google Gemini (non privé) |
|
||||
|
||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||
|
||||
## Config
|
||||
|
||||
Exemple de configuration :
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:1.5b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en fr
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**Explication du fichier config.ini**:
|
||||
|
||||
`is_local` -> Exécute l’agent localement (True) ou sur un serveur distant (False).
|
||||
|
||||
`provider_name` -> Le fournisseur à utiliser (parmi : ollama, server, lm-studio, deepseek-api).
|
||||
|
||||
`provider_model` -> Le modèle utilisé, par exemple, deepseek-r1:1.5b.
|
||||
|
||||
`provider_server_address` -> Adresse du serveur, par exemple, 127.0.0.1:11434 pour local. Définissez n’importe quoi pour une API non locale.
|
||||
|
||||
`agent_name` -> Nom de l’agent, par exemple, Friday. Utilisé comme mot déclencheur pour la reconnaissance vocale.
|
||||
|
||||
`recover_last_session` -> Reprend la dernière session (True) ou non (False).
|
||||
|
||||
`save_session` -> Sauvegarde les données de la session (True) ou non (False).
|
||||
|
||||
`speak` -> Active la sortie vocale (True) ou non (False).
|
||||
|
||||
`listen` -> Écoute les entrées vocales (True) ou non (False).
|
||||
|
||||
`work_dir` -> Dossier auquel l’IA aura accès, par exemple : /Users/user/Documents/.
|
||||
|
||||
`jarvis_personality` -> Utilise une personnalité inspiré de Jarvis (True) ou non (False). Cela utilise simplement une prompt alternative. Marche moins bien en français.
|
||||
|
||||
`headless_browser` -> Exécute le navigateur sans fenêtre visible (True) ou non (False).
|
||||
|
||||
`stealth_mode` -> Rend la détection des bots plus difficile. Le seul inconvénient est que vous devez installer manuellement l’extension anticaptcha.
|
||||
|
||||
`languages` -> La liste de languages supportés (nécessaire pour le routage d'agents). Plus la liste est longue. Plus un nombre important de modèles sera téléchargés.
|
||||
|
||||
## Providers
|
||||
|
||||
Le tableau ci-dessous montre les LLM providers disponibles :
|
||||
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | Yes | Exécutez des LLM localement avec facilité en utilisant Ollama comme fournisseur LLM
|
||||
| server | Yes | Hébergez le modèle sur une autre machine, exécutez sur votre machine locale
|
||||
| lm-studio | Yes | Exécutez un LLM localement avec LM Studio (définissez provider_name sur lm-studio)
|
||||
| openai | No | Utilise l'API ChatGPT (pas privé) |
|
||||
| deepseek-api | No | Utilise l'API Deepseek (pas privé) |
|
||||
| huggingface| No | Utilise Hugging-Face (pas privé) |
|
||||
| together| No | Utilise l'api Together AI |
|
||||
|
||||
Pour sélectionner un provider LLM, modifiez le config.ini :
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
|
||||
`is_local` : doit être True pour tout LLM exécuté localement, sinon False.
|
||||
|
||||
`provider_name` : Sélectionnez le fournisseur à utiliser par son nom, voir la liste des fournisseurs ci-dessus.
|
||||
|
||||
`provider_model` : Définissez le modèle à utiliser par l’agent.
|
||||
|
||||
`provider_server_address` : peut être défini sur n’importe quoi si vous n’utilisez pas le fournisseur server.
|
||||
|
||||
# Problèmes connus
|
||||
|
||||
## Problèmes avec Chromedriver
|
||||
|
||||
Erreur #1:**incompatibilité**
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
Cela se produit s’il y a une incompatibilité entre votre navigateur et la version de chromedriver.
|
||||
|
||||
Vous devez naviguer pour télécharger la dernière version :
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
Si vous utilisez Chrome version 115 ou plus récent, allez sur :
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
Et téléchargez la version de chromedriver correspondant à votre système d’exploitation.
|
||||
|
||||

|
||||
|
||||
Si cette section est incomplète, merci de faire une nouvelle issue sur github.
|
||||
|
||||
## FAQ
|
||||
**Q: Quel matériel est nécessaire ?**
|
||||
|
||||
| Taille du Modèle | GPU | Commentaire |
|
||||
|--------------------|------|----------------------------------------------------------|
|
||||
| 7B | 8 Go VRAM | ⚠️ Non recommandé. Performances médiocres, hallucinations fréquentes, et l'agent planificateur échouera probablement. |
|
||||
| 14B | 12 Go VRAM (par ex. RTX 3060) | ✅ Utilisable pour des tâches simples. Peut rencontrer des difficultés avec la navigation web et les tâches de planification. |
|
||||
| 32B | 24+ Go VRAM (par ex. RTX 4090) | 🚀 Réussite avec la plupart des tâches, peut encore avoir des difficultés avec la planification des tâches. |
|
||||
| 70B+ | 48+ Go VRAM (par ex. Mac Studio) | 💪 Excellent. Recommandé pour des cas d'utilisation avancés. |
|
||||
|
||||
**Q: Pourquoi deepseek et pas un autre modèle**
|
||||
|
||||
DeepSeek R1 excelle dans le raisonnement et l’utilisation d’outils pour sa taille. Nous pensons que c’est un choix solide pour nos besoins, bien que d’autres modèles fonctionnent également (bien que moins bien pour un nombre équivalent de paramètres).
|
||||
|
||||
**Q: J'ai une erreur quand je lance le programme, je fait quoi?**
|
||||
|
||||
Assurez-vous qu’Ollama est en cours d’exécution (ollama serve), que votre config.ini correspond à votre fournisseur, et que les dépendances sont installées. Si cela ne fonctionne pas, n’hésitez pas à signaler un problème.
|
||||
|
||||
**Q: C'est vraiment 100% local?**
|
||||
|
||||
Oui, avec les fournisseurs Ollama, lm-studio ou Server, toute la reconnaissance vocale, le LLM et la synthèse vocale fonctionnent localement. Les options non locales (OpenAI ou autres API) sont facultatives.
|
||||
|
||||
**Q: En quoi c'est supérieur à Manus**
|
||||
|
||||
Il ne l'est certainement pas, mais nous privilégions l’exécution locale et la confidentialité par rapport à une approche basée sur le cloud. C’est une alternative plus accessible et surtout moins cher !
|
||||
|
||||
## Contribution
|
||||
|
||||
Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la section "issues" github ou les discussions.
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
||||
|
||||
## Auteurs/Mainteneurs:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
@@ -0,0 +1,480 @@
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語
|
||||
|
||||
# AgenticSeek: Deepseek R1エージェントによって動作するManusのようなAI。
|
||||
|
||||
|
||||
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **進行中の作業** – 貢献者を探しています!
|
||||
|
||||
|
||||
|
||||
|
||||
https://github.com/user-attachments/assets/fe9e8006-0462-4793-8b31-25bd42c6d1eb
|
||||
|
||||
|
||||
|
||||
|
||||
*そしてもっと多くのことができます!*
|
||||
|
||||
> *大阪と東京のAIスタートアップを深く調査し、少なくとも5つ見つけて、research_japan.txtファイルに保存してください*
|
||||
|
||||
> *C言語でテトリスゲームを作れますか?*
|
||||
|
||||
> *新しいプロジェクトファイルインデックスをmark2として設定したいです。*
|
||||
|
||||
|
||||
## 特徴:
|
||||
|
||||
- **100%ローカル**: クラウドなし、ハードウェア上で動作。データはあなたのものです。
|
||||
|
||||
- **ファイルシステムの操作**: bashを使用してファイルを簡単にナビゲートおよび操作します。
|
||||
|
||||
- **自律的なコーディング**: Python、C、Golangなどのコードを書き、デバッグし、実行できます。
|
||||
|
||||
- **エージェントルーティング**: タスクに最適なエージェントを自動的に選択します。
|
||||
|
||||
- **計画**: 複雑なタスクの場合、複数のエージェントを起動して計画および実行します。
|
||||
|
||||
- **自律的なウェブブラウジング**: 自律的なウェブナビゲーション。
|
||||
|
||||
- **メモリ**: 効率的なメモリとセッション管理。
|
||||
|
||||
---
|
||||
|
||||
## **インストール**
|
||||
|
||||
chrome driver、docker、およびpython3.10(またはそれ以降)がインストールされていることを確認してください。
|
||||
|
||||
chrome driverに関連する問題については、**Chromedriver**セクションを参照してください。
|
||||
|
||||
### 1️⃣ **リポジトリをクローンしてセットアップ**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2️ **仮想環境を作成**
|
||||
|
||||
```sh
|
||||
python3 -m venv agentic_seek_env
|
||||
source agentic_seek_env/bin/activate
|
||||
# Windowsの場合: agentic_seek_env\Scripts\activate
|
||||
```
|
||||
|
||||
### 3️⃣ **パッケージをインストール**
|
||||
|
||||
**自動インストール:**
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
**手動で:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# または
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ローカルマシンでLLMを実行するためのセットアップ
|
||||
|
||||
**少なくともDeepseek 14Bを使用することをお勧めします。小さいモデルでは、特にウェブブラウジングのタスクで苦労する可能性があります。**
|
||||
|
||||
**ローカルプロバイダーをセットアップする**
|
||||
|
||||
たとえば、ollamaを使用してローカルプロバイダーを開始します:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
以下に、サポートされているローカルプロバイダーのリストを示します。
|
||||
|
||||
**config.iniを更新する**
|
||||
|
||||
config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model`を`deepseek-r1:14b`に設定します。
|
||||
|
||||
注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # または lm-studio、openai など
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
```
|
||||
|
||||
**ローカルプロバイダーのリスト**
|
||||
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
|
||||
| lm-studio | はい | LM studioを使用してローカルでLLMを実行(`provider_name`を`lm-studio`に設定)|
|
||||
| openai | はい | OpenAI互換APIを使用 |
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
|
||||
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
|
||||
|
||||
*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください。*
|
||||
|
||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||
|
||||
---
|
||||
|
||||
## APIを使用したセットアップ
|
||||
|
||||
`config.ini`で希望するプロバイダーを設定してください。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
|
||||
警告: `config.ini`に末尾のスペースがないことを確認してください。
|
||||
|
||||
ローカルのOpenAIベースのAPIを使用する場合は、`is_local`をTrueに設定してください。
|
||||
|
||||
OpenAIベースのAPIが独自のサーバーで実行されている場合は、IPアドレスを変更してください。
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
|
||||
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
|
||||
|
||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||
|
||||
---
|
||||
|
||||
## サービスの開始と実行
|
||||
|
||||
必要に応じてPython環境をアクティブにしてください。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
|
||||
- searxng
|
||||
- redis (searxngに必要)
|
||||
- フロントエンド
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
```
|
||||
|
||||
**オプション1:** CLIインターフェースで実行。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**オプション2:** Webインターフェースで実行。
|
||||
|
||||
注意: 現在、CLIの使用を推奨しています。Webインターフェースは開発中です。
|
||||
|
||||
バックエンドを開始します。
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
`http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。
|
||||
|
||||
現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。
|
||||
|
||||
---
|
||||
|
||||
## 使い方
|
||||
|
||||
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
||||
|
||||
サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
`>>> `と表示されます
|
||||
これは、agenticSeekが指示を待っていることを示します。
|
||||
configで`listen = True`を設定することで、音声認識を使用することもできます。
|
||||
|
||||
終了するには、単に`goodbye`と言います。
|
||||
|
||||
以下は使用例です:
|
||||
|
||||
### コーディング/バッシュ
|
||||
|
||||
> *Pythonでスネークゲームを作成*
|
||||
|
||||
> *C言語で行列の掛け算を教えて*
|
||||
|
||||
> *Golangでブラックジャックを作成*
|
||||
|
||||
### ウェブ検索
|
||||
|
||||
> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う*
|
||||
|
||||
> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?*
|
||||
|
||||
> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?*
|
||||
|
||||
### ファイルシステム
|
||||
|
||||
> *契約書.pdfがどこにあるか見つけてくれませんか?*
|
||||
|
||||
> *ディスクにどれだけの空き容量があるか教えて*
|
||||
|
||||
> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください*
|
||||
|
||||
### カジュアル
|
||||
|
||||
> *フランスのレンヌについて教えて*
|
||||
|
||||
> *博士号を追求すべきですか?*
|
||||
|
||||
> *最高のワークアウトルーチンは何ですか?*
|
||||
|
||||
|
||||
クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。
|
||||
|
||||
これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
||||
|
||||
したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください:
|
||||
|
||||
`一人旅に良い国を知っていますか?`
|
||||
|
||||
代わりに、次のように尋ねてください:
|
||||
|
||||
`ウェブ検索を行い、一人旅に最適な国を見つけてください`
|
||||
|
||||
---
|
||||
|
||||
## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ**
|
||||
|
||||
強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。
|
||||
|
||||
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
|
||||
curl https://ipinfo.io/ip # 公開IP
|
||||
```
|
||||
|
||||
注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください。
|
||||
|
||||
リポジトリをクローンし、`server/`フォルダーに移動します。
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
サーバー固有の依存関係をインストールします:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
サーバースクリプトを実行します。
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
`ollama`と`llamacpp`のどちらかをLLMサービスとして選択できます。
|
||||
|
||||
次に、個人用コンピュータで以下を行います:
|
||||
|
||||
`config.ini`ファイルを変更し、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。
|
||||
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:70b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 音声認識
|
||||
|
||||
現在、音声認識は英語でのみ動作することに注意してください。
|
||||
|
||||
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイルの`agent_name`値を更新することでカスタマイズできます:
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
最適な認識のために、"John"や"Emma"のような一般的な英語の名前をエージェント名として使用することをお勧めします。
|
||||
|
||||
トランスクリプトが表示され始めたら、エージェントの名前を大声で言って起動します(例:"Friday")。
|
||||
|
||||
クエリを明確に話します。
|
||||
|
||||
リクエストを終了する際に確認フレーズを使用してシステムに進行を通知します。確認フレーズの例には次のようなものがあります:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## 設定
|
||||
|
||||
設定例:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:1.5b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en ja
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**説明**:
|
||||
|
||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
|
||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)。
|
||||
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
|
||||
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
|
||||
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます。
|
||||
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
|
||||
- save_session -> セッションデータを保存する(True)か、しない(False)。
|
||||
- speak -> 音声出力を有効にする(True)か、しない(False)。
|
||||
- listen -> 音声入力を有効にする(True)か、しない(False)。
|
||||
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
|
||||
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)か、しない(False)。これは単にプロンプトファイルを変更するだけです。
|
||||
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
|
||||
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
||||
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
|
||||
|
||||
## プロバイダー
|
||||
|
||||
以下の表は利用可能なプロバイダーを示しています:
|
||||
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
|
||||
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
|
||||
| lm-studio | はい | LM studio(`lm-studio`)を使用してローカルでLLMを実行 |
|
||||
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
|
||||
| deepseek-api | いいえ | Deepseek API(非プライベート) |
|
||||
| huggingface| いいえ | Hugging-Face API(非プライベート) |
|
||||
| togetherAI | いいえ | together AI API(非プライベート)を使用
|
||||
|
||||
|
||||
プロバイダーを選択するには、config.iniを変更します:
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。
|
||||
|
||||
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
|
||||
|
||||
`provider_model`: エージェントが使用するモデルを設定します。
|
||||
|
||||
`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます。
|
||||
|
||||
# 既知の問題
|
||||
|
||||
## Chromedriverの問題
|
||||
|
||||
**既知のエラー#1:** *chromedriverの不一致*
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
|
||||
|
||||
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
お使いのOSに対応するchromedriverバージョンをダウンロードします。
|
||||
|
||||

|
||||
|
||||
このセクションが不完全な場合は、問題を報告してください。
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: どのようなハードウェアが必要ですか?**
|
||||
|
||||
| モデルサイズ | GPU | コメント |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスが低く、頻繁に幻覚を起こし、プランナーエージェントが失敗する可能性が高いです。 |
|
||||
| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 |
|
||||
| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 |
|
||||
| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 |
|
||||
|
||||
**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?**
|
||||
|
||||
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
|
||||
|
||||
**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
|
||||
|
||||
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
|
||||
|
||||
**Q: 本当に100%ローカルで実行できますか?**
|
||||
|
||||
はい、OllamaまたはServerプロバイダーを使用すると、すべての音声認識、LLM、および音声合成モデルがローカルで実行されます。非ローカルオプション(OpenAIまたは他のAPI)はオプションです。
|
||||
|
||||
**Q: Manusを持っているのに、なぜAgenticSeekを使用する必要があるのですか?**
|
||||
|
||||
これは、AIエージェントに関する興味から始まったサイドプロジェクトです。特別な点は、ローカルモデルを使用し、APIを避けることです。
|
||||
私たちは、JarvisやFriday(アイアンマン映画)からインスピレーションを得て、「クール」にしようとしましたが、機能性に関してはManusから多くのインスピレーションを得ています。なぜなら、人々が最初に求めているのはローカルのManusの代替品だからです。
|
||||
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIのコストを回避します。
|
||||
|
||||
## 貢献
|
||||
|
||||
AgenticSeekを改善するための開発者を探しています!オープンな問題やディスカッションを確認してください。
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
## 著者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os, sys
|
||||
import uvicorn
|
||||
import aiofiles
|
||||
import configparser
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import uuid
|
||||
|
||||
from sources.llm_provider import Provider
|
||||
from sources.interaction import Interaction
|
||||
from sources.agents import CasualAgent, CoderAgent, FileAgent, PlannerAgent, BrowserAgent
|
||||
from sources.browser import Browser, create_driver
|
||||
from sources.utility import pretty_print
|
||||
from sources.logger import Logger
|
||||
from sources.schemas import QueryRequest, QueryResponse
|
||||
|
||||
|
||||
from celery import Celery
|
||||
|
||||
api = FastAPI(title="AgenticSeek API", version="0.1.0")
|
||||
celery_app = Celery("tasks", broker="redis://localhost:6379/0", backend="redis://localhost:6379/0")
|
||||
celery_app.conf.update(task_track_started=True)
|
||||
logger = Logger("backend.log")
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
api.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
if not os.path.exists(".screenshots"):
|
||||
os.makedirs(".screenshots")
|
||||
api.mount("/screenshots", StaticFiles(directory=".screenshots"), name="screenshots")
|
||||
|
||||
def initialize_system():
|
||||
stealth_mode = config.getboolean('BROWSER', 'stealth_mode')
|
||||
personality_folder = "jarvis" if config.getboolean('MAIN', 'jarvis_personality') else "base"
|
||||
languages = config["MAIN"]["languages"].split(' ')
|
||||
|
||||
provider = Provider(
|
||||
provider_name=config["MAIN"]["provider_name"],
|
||||
model=config["MAIN"]["provider_model"],
|
||||
server_address=config["MAIN"]["provider_server_address"],
|
||||
is_local=config.getboolean('MAIN', 'is_local')
|
||||
)
|
||||
logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")
|
||||
|
||||
browser = Browser(
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
|
||||
anticaptcha_manual_install=stealth_mode
|
||||
)
|
||||
logger.info("Browser initialized")
|
||||
|
||||
agents = [
|
||||
CasualAgent(
|
||||
name=config["MAIN"]["agent_name"],
|
||||
prompt_path=f"prompts/{personality_folder}/casual_agent.txt",
|
||||
provider=provider, verbose=False
|
||||
),
|
||||
CoderAgent(
|
||||
name="coder",
|
||||
prompt_path=f"prompts/{personality_folder}/coder_agent.txt",
|
||||
provider=provider, verbose=False
|
||||
),
|
||||
FileAgent(
|
||||
name="File Agent",
|
||||
prompt_path=f"prompts/{personality_folder}/file_agent.txt",
|
||||
provider=provider, verbose=False
|
||||
),
|
||||
BrowserAgent(
|
||||
name="Browser",
|
||||
prompt_path=f"prompts/{personality_folder}/browser_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser
|
||||
),
|
||||
PlannerAgent(
|
||||
name="Planner",
|
||||
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser
|
||||
)
|
||||
]
|
||||
logger.info("Agents initialized")
|
||||
|
||||
interaction = Interaction(
|
||||
agents,
|
||||
tts_enabled=config.getboolean('MAIN', 'speak'),
|
||||
stt_enabled=config.getboolean('MAIN', 'listen'),
|
||||
recover_last_session=config.getboolean('MAIN', 'recover_last_session'),
|
||||
langs=languages
|
||||
)
|
||||
logger.info("Interaction initialized")
|
||||
return interaction
|
||||
|
||||
interaction = initialize_system()
|
||||
is_generating = False
|
||||
query_resp_history = []
|
||||
|
||||
@api.get("/screenshot")
|
||||
async def get_screenshot():
|
||||
logger.info("Screenshot endpoint called")
|
||||
screenshot_path = ".screenshots/updated_screen.png"
|
||||
if os.path.exists(screenshot_path):
|
||||
return FileResponse(screenshot_path)
|
||||
logger.error("No screenshot available")
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"error": "No screenshot available"}
|
||||
)
|
||||
|
||||
@api.get("/health")
|
||||
async def health_check():
|
||||
logger.info("Health check endpoint called")
|
||||
return {"status": "healthy", "version": "0.1.0"}
|
||||
|
||||
@api.get("/is_active")
|
||||
async def is_active():
|
||||
logger.info("Is active endpoint called")
|
||||
return {"is_active": interaction.is_active}
|
||||
|
||||
@api.get("/latest_answer")
|
||||
async def get_latest_answer():
|
||||
global query_resp_history
|
||||
if interaction.current_agent is None:
|
||||
return JSONResponse(status_code=404, content={"error": "No agent available"})
|
||||
uid = str(uuid.uuid4())
|
||||
if not any(q["answer"] == interaction.current_agent.last_answer for q in query_resp_history):
|
||||
query_resp = {
|
||||
"done": "false",
|
||||
"answer": interaction.current_agent.last_answer,
|
||||
"agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
|
||||
"success": interaction.current_agent.success,
|
||||
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
|
||||
"status": interaction.current_agent.get_status_message if interaction.current_agent else "No status available",
|
||||
"uid": uid
|
||||
}
|
||||
interaction.current_agent.last_answer = ""
|
||||
query_resp_history.append(query_resp)
|
||||
return JSONResponse(status_code=200, content=query_resp)
|
||||
if query_resp_history:
|
||||
return JSONResponse(status_code=200, content=query_resp_history[-1])
|
||||
return JSONResponse(status_code=404, content={"error": "No answer available"})
|
||||
|
||||
async def think_wrapper(interaction, query, tts_enabled):
|
||||
try:
|
||||
interaction.tts_enabled = tts_enabled
|
||||
interaction.last_query = query
|
||||
logger.info("Agents request is being processed")
|
||||
success = await interaction.think()
|
||||
if not success:
|
||||
interaction.last_answer = "Error: No answer from agent"
|
||||
interaction.last_success = False
|
||||
else:
|
||||
interaction.last_success = True
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.error(f"Error in think_wrapper: {str(e)}")
|
||||
interaction.last_answer = f"Error: {str(e)}"
|
||||
interaction.last_success = False
|
||||
raise e
|
||||
|
||||
@api.post("/query", response_model=QueryResponse)
|
||||
async def process_query(request: QueryRequest):
|
||||
global is_generating, query_resp_history
|
||||
logger.info(f"Processing query: {request.query}")
|
||||
query_resp = QueryResponse(
|
||||
done="false",
|
||||
answer="",
|
||||
agent_name="Unknown",
|
||||
success="false",
|
||||
blocks={},
|
||||
status="Ready",
|
||||
uid=str(uuid.uuid4())
|
||||
)
|
||||
if is_generating:
|
||||
logger.warning("Another query is being processed, please wait.")
|
||||
return JSONResponse(status_code=429, content=query_resp.jsonify())
|
||||
|
||||
try:
|
||||
is_generating = True
|
||||
success = await think_wrapper(interaction, request.query, request.tts_enabled)
|
||||
is_generating = False
|
||||
|
||||
if not success:
|
||||
query_resp.answer = interaction.last_answer
|
||||
return JSONResponse(status_code=400, content=query_resp.jsonify())
|
||||
|
||||
if interaction.current_agent:
|
||||
blocks_json = {f'{i}': block.jsonify() for i, block in enumerate(interaction.current_agent.get_blocks_result())}
|
||||
else:
|
||||
logger.error("No current agent found")
|
||||
blocks_json = {}
|
||||
query_resp.answer = "Error: No current agent"
|
||||
return JSONResponse(status_code=400, content=query_resp.jsonify())
|
||||
|
||||
logger.info(f"Answer: {interaction.last_answer}")
|
||||
logger.info(f"Blocks: {blocks_json}")
|
||||
query_resp.done = "true"
|
||||
query_resp.answer = interaction.last_answer
|
||||
query_resp.agent_name = interaction.current_agent.agent_name
|
||||
query_resp.success = str(interaction.last_success)
|
||||
query_resp.blocks = blocks_json
|
||||
|
||||
# Store the raw dictionary representation
|
||||
query_resp_dict = {
|
||||
"done": query_resp.done,
|
||||
"answer": query_resp.answer,
|
||||
"agent_name": query_resp.agent_name,
|
||||
"success": query_resp.success,
|
||||
"blocks": query_resp.blocks,
|
||||
"status": query_resp.status,
|
||||
"uid": query_resp.uid
|
||||
}
|
||||
query_resp_history.append(query_resp_dict)
|
||||
|
||||
logger.info("Query processed successfully")
|
||||
return JSONResponse(status_code=200, content=query_resp.jsonify())
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
logger.info("Processing finished")
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(api, host="0.0.0.0", port=8000)
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin python3
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import configparser
|
||||
import asyncio
|
||||
|
||||
from sources.llm_provider import Provider
|
||||
from sources.interaction import Interaction
|
||||
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
|
||||
from sources.browser import Browser, create_driver
|
||||
from sources.utility import pretty_print
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
async def main():
|
||||
pretty_print("Initializing...", color="status")
|
||||
stealth_mode = config.getboolean('BROWSER', 'stealth_mode')
|
||||
personality_folder = "jarvis" if config.getboolean('MAIN', 'jarvis_personality') else "base"
|
||||
languages = config["MAIN"]["languages"].split(' ')
|
||||
|
||||
provider = Provider(provider_name=config["MAIN"]["provider_name"],
|
||||
model=config["MAIN"]["provider_model"],
|
||||
server_address=config["MAIN"]["provider_server_address"],
|
||||
is_local=config.getboolean('MAIN', 'is_local'))
|
||||
|
||||
browser = Browser(
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
|
||||
anticaptcha_manual_install=stealth_mode
|
||||
)
|
||||
|
||||
agents = [
|
||||
CasualAgent(name=config["MAIN"]["agent_name"],
|
||||
prompt_path=f"prompts/{personality_folder}/casual_agent.txt",
|
||||
provider=provider, verbose=False),
|
||||
CoderAgent(name="coder",
|
||||
prompt_path=f"prompts/{personality_folder}/coder_agent.txt",
|
||||
provider=provider, verbose=False),
|
||||
FileAgent(name="File Agent",
|
||||
prompt_path=f"prompts/{personality_folder}/file_agent.txt",
|
||||
provider=provider, verbose=False),
|
||||
BrowserAgent(name="Browser",
|
||||
prompt_path=f"prompts/{personality_folder}/browser_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser),
|
||||
PlannerAgent(name="Planner",
|
||||
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser)
|
||||
]
|
||||
|
||||
interaction = Interaction(agents,
|
||||
tts_enabled=config.getboolean('MAIN', 'speak'),
|
||||
stt_enabled=config.getboolean('MAIN', 'listen'),
|
||||
recover_last_session=config.getboolean('MAIN', 'recover_last_session'),
|
||||
langs=languages
|
||||
)
|
||||
try:
|
||||
while interaction.is_active:
|
||||
interaction.get_user()
|
||||
if await interaction.think():
|
||||
interaction.show_answer()
|
||||
except Exception as e:
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
raise e
|
||||
finally:
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -2,10 +2,15 @@
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = True
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = True
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/A-project/AI/Agents/agenticSeek/ai_workplace
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = True
|
||||
@@ -0,0 +1,105 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
redis:
|
||||
container_name: redis
|
||||
image: docker.io/valkey/valkey:8-alpine
|
||||
command: valkey-server --save 30 1 --loglevel warning
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- SETGID
|
||||
- SETUID
|
||||
- DAC_OVERRIDE
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "1m"
|
||||
max-file: "1"
|
||||
networks:
|
||||
- agentic-seek-net
|
||||
|
||||
searxng:
|
||||
container_name: searxng
|
||||
image: docker.io/searxng/searxng:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./searxng:/etc/searxng:rw
|
||||
environment:
|
||||
- SEARXNG_BASE_URL=http://localhost:8080/
|
||||
- SEARXNG_SECRET_KEY=$(openssl rand -hex 32)
|
||||
- UWSGI_WORKERS=4
|
||||
- UWSGI_THREADS=4
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "1m"
|
||||
max-file: "1"
|
||||
depends_on:
|
||||
- redis
|
||||
networks:
|
||||
- agentic-seek-net
|
||||
|
||||
frontend:
|
||||
container_name: frontend
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile.frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./frontend/agentic-seek-front/src:/app/src
|
||||
- ./screenshots:/app/screenshots
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
- BACKEND_URL=http://backend:8000
|
||||
networks:
|
||||
- agentic-seek-net
|
||||
|
||||
# NOTE: backend service is not working yet due to issue with chromedriver on docker.
|
||||
# Therefore backend is run on host machine.
|
||||
# Open to pull requests to fix this.
|
||||
|
||||
#backend:
|
||||
# container_name: backend
|
||||
# build:
|
||||
# context: ./
|
||||
# dockerfile: Dockerfile.backend
|
||||
# stdin_open: true
|
||||
# tty: true
|
||||
# shm_size: 8g
|
||||
# ports:
|
||||
# - "8000:8000"
|
||||
# volumes:
|
||||
# - ./:/app
|
||||
# environment:
|
||||
# - NODE_ENV=development
|
||||
# - REDIS_URL=redis://redis:6379/0
|
||||
# - SEARXNG_URL=http://searxng:8080
|
||||
# - OLLAMA_URL=http://localhost:11434
|
||||
# - LM_STUDIO_URL=http://localhost:1234
|
||||
# extra_hosts:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
# depends_on:
|
||||
# - redis
|
||||
# - searxng
|
||||
# networks:
|
||||
# - agentic-seek-net
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
chrome_profiles:
|
||||
|
||||
networks:
|
||||
agentic-seek-net:
|
||||
driver: bridge
|
||||
@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
@@ -22,17 +22,17 @@ community include:
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
@@ -52,15 +52,15 @@ decisions when appropriate.
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
reported to the community leaders responsible for enforcement:
|
||||
you need to send a private message to `fossowl` or `mow8758` on discord.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
@@ -109,20 +109,24 @@ Violating these terms may lead to a permanent ban.
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -0,0 +1,297 @@
|
||||
# Contributors guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.10 or higher.
|
||||
- Docker or Orbstack or Podman.
|
||||
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
|
||||
- Basic familiarity with Python and AI models.
|
||||
- Join the discord (optional): https://discord.gg/8hGDaME3TC
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
We welcome contributions in the following areas:
|
||||
|
||||
- Code Improvements: Optimize existing code, fix bugs, or add new features.
|
||||
- Documentation: Improve the README, write tutorials, or add inline comments.
|
||||
- Testing: Write unit tests, integration tests, or help with debugging.
|
||||
- New Features: Implement new tools, agents, or integrations.
|
||||
|
||||
## Steps to Contribute
|
||||
|
||||
Fork the project to your GitHub account.
|
||||
|
||||
Create a Branch:
|
||||
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
Make Your Changes.
|
||||
|
||||
Write your code, add documentation, or fix bugs.
|
||||
|
||||
Test Your Changes.
|
||||
|
||||
Ensure your changes work as expected and do not break existing functionality.
|
||||
|
||||
Push your changes to your fork and submit a pull request to the main branch of this repository. Provide a clear description of your changes and reference any related issues.
|
||||
|
||||
## Good practice
|
||||
|
||||
1. **Privacy First, Always Local**
|
||||
- All core functionality must be able to run 100% locally
|
||||
- Cloud services should only be optional alternatives, clearly defined with a warning message.
|
||||
- remote APIs are only allowed for specific tools (weather api, MCP, flight search, etc...)
|
||||
- User data privacy is non-negotiable
|
||||
|
||||
2. **Agent-Based Architecture**
|
||||
- Each agent should have a clear, single responsibility
|
||||
- Agents should be modular and independently testable
|
||||
- New agents should solve specific use cases
|
||||
|
||||
3. **Tool-Based Extensibility**
|
||||
- Tools should be self-contained and follow the Tools base class
|
||||
- Each tool should do one thing well
|
||||
- Tools should provide clear feedback on success/failure
|
||||
|
||||
4. **User Experience**
|
||||
- Provide meaningful feedback for all operations
|
||||
- Support multiple languages
|
||||
- Text to speech with short response.
|
||||
- Keep responses concise
|
||||
|
||||
5. **Code Quality**
|
||||
- Write clear, self-documenting code
|
||||
- Include type hints and docstrings
|
||||
- Follow existing patterns in the codebase
|
||||
- Add a if __name__ == "__main__" at the bottom of each class file for individual testing.
|
||||
- Ideally had automated tests.
|
||||
|
||||
6. **Error Handling**
|
||||
- Fail gracefully with meaningful messages
|
||||
- Include recovery mechanisms where possible
|
||||
- Log errors appropriately without exposing sensitive data
|
||||
|
||||
## Areas Needing Help
|
||||
|
||||
Here are some tasks and areas where we need contributions:
|
||||
|
||||
- Web Browsing: Improve the autonomous web browsing capabilities for the assistant.
|
||||
- Graphical interface, a web graphical interface. (please ask first)
|
||||
- Multi-Agent System: Enhance the planner agent for divide and conqueer for task (please ask first).
|
||||
- New Tools: Add support for additional programming languages or APIs.
|
||||
- MCP: Add MCP protocol compatibility (possibly as a special type tool).
|
||||
- Multi-language support: for Text to speech & speech to text
|
||||
- Prompt engineering: improve prompts, compare results with different prompts for a identical query. Iterate until you find better prompt.
|
||||
- Bug hunt: Hunt and fix bugs.
|
||||
- Crossplatform: enhance cross-platform support.
|
||||
- Testing: Write comprehensive tests for existing features.
|
||||
|
||||
# Implementing and using Tools
|
||||
|
||||
Tools are extensions that enable agents to perform specific actions, such as running Python code, making API calls, or conducting web searches. All tools inherit from the Tools base class, which provides methods for parsing and executing tool instructions.
|
||||
|
||||
## Tools parsing
|
||||
|
||||
Agents invoke tools using a standardized format called a block. A block consists of the tool name followed by the content (e.g., code, query, or parameters) to execute. The format looks like this:
|
||||
|
||||
BECAUSE WE USE MARKDOWN QUOTE FORMAT, READING WILL BE BROKEN ON GITHUB PLEASE START READING THE FILE AS RAW: https://raw.githubusercontent.com/Fosowl/agenticSeek/refs/heads/main/CONTRIBUTING.md
|
||||
|
||||
|
||||
```<tool name>
|
||||
<code or query to execute>
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```web_search
|
||||
What to do in Taipei?
|
||||
```
|
||||
|
||||
we call these "blocks".
|
||||
|
||||
The Tools class provides the load_exec_block method to extract and parse blocks from an agent's response. This method identifies the tool name and content, enabling the system to execute the appropriate action.
|
||||
|
||||
How to handle multiple arguments then ?
|
||||
|
||||
Good question! Each tool is free to handle argument in it's own way within the block, but we provide a common parsing logic:
|
||||
|
||||
```flight_search
|
||||
from=Paris
|
||||
to=Taipei
|
||||
date=30/04/2026
|
||||
```
|
||||
|
||||
To extract these parameters, use the `get_parameter_value` method provided by the Tools class. Each tool can define its own parameter-handling logic, but the Tools class ensures consistent parsing.
|
||||
|
||||
Again if a tool need a specific format, you could implement a specific method for parsing a block. Using get_parameter_value is optional.
|
||||
|
||||
The content of blocks can also be saved using :path, for instance:
|
||||
|
||||
```python:toto.py
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
Will save the code in toto.py file within the work_folder defined in the config.ini
|
||||
|
||||
## Execution
|
||||
|
||||
When developing a tool, you must implement three abstract methods defined in the Tools class to handle execution, failure detection, and feedback to the agent. These methods ensure consistent behavior across tools and enable robust interaction with the LLM.
|
||||
|
||||
### 1. Execute method
|
||||
|
||||
```
|
||||
@abstractmethod
|
||||
def execute(self, blocks: [str], safety: bool) -> str:
|
||||
```
|
||||
|
||||
This method defines how the tool processes the provided block(s) and produces a result.
|
||||
|
||||
### 2. execution_failure_check
|
||||
|
||||
```
|
||||
@abstractmethod
|
||||
def execution_failure_check(self, output: str) -> bool:
|
||||
```
|
||||
|
||||
This method analyzes the tool’s output to determine if the execution was successful or failed.
|
||||
|
||||
### 3. interpreter_feedback
|
||||
|
||||
```
|
||||
@abstractmethod
|
||||
def interpreter_feedback(self, output: str) -> str:
|
||||
```
|
||||
|
||||
This method generates a feedback message for the LLM, helping it understand the tool’s execution outcome and adjust its behavior if needed.
|
||||
|
||||
Recap:
|
||||
- load_exec_block: Extracts and parses tool blocks from the agent's response.
|
||||
- get_parameter_value: Retrieves parameter values from a block's content.
|
||||
- File handling: Supports saving block content to files when a :path is specified.
|
||||
|
||||
# Implementing and using Agents
|
||||
|
||||
|
||||
Agents are classes that define how an LLM interacts with users and processes inputs. They can use tools (e.g., for executing code or querying APIs) and maintain a memory of the conversation to provide context-aware responses. All agents inherit from the base Agent class, which provides core functionality like memory management and LLM communication.
|
||||
|
||||
The simplest agent example is the casual agent:
|
||||
|
||||
```
|
||||
class CasualAgent(Agent):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
"""
|
||||
The casual agent is a special for casual talk to the user without specific tasks.
|
||||
"""
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
self.tools = {
|
||||
} # No tools for the casual agent
|
||||
self.role = "en"
|
||||
self.type = "casual_agent"
|
||||
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
self.memory.push('user', prompt)
|
||||
animate_thinking("Thinking...", color="status")
|
||||
answer, reasoning = self.llm_request()
|
||||
self.last_answer = answer
|
||||
return answer, reasoning
|
||||
```
|
||||
|
||||
Agent have several parameters that should be sets:
|
||||
|
||||
`tools`: A dictionary of tools the agent can use. Each tool must inherit from the Tools class. For example, a CasualAgent has no tools ({}), while a coding agent might include a Python execution tool.
|
||||
|
||||
`role`:A dictionary defining the agent's role, used by the routing system to select the appropriate agent.
|
||||
`type: the agent type, a fixed name to identify the unique agent type.
|
||||
|
||||
Every agent must implement the process method, which defines how it handles user input and generates a response.
|
||||
|
||||
**Workflow:**
|
||||
|
||||
Push the user's prompt to the agent's memory using self.memory.push('user', prompt).
|
||||
Call self.llm_request() to generate a response and reasoning based on the memory context.
|
||||
Store and return the response and reasoning.
|
||||
|
||||
Note the memory logic. You only need to push the 'user' message. The llm_request method take care of pushing the assistant message.
|
||||
|
||||
This separation of user and assistant memory handling may be inconsistent and could be refactored for clarity in the near future.
|
||||
|
||||
**Tool blocks execution**
|
||||
|
||||
Each agent might return block of tool to execute, as explained in the **Implementing and using Tools** section.
|
||||
|
||||
In a single text returned by an agent, a succession of block might be present for example, the coding agent answer could be:
|
||||
|
||||
I will create a work folder:
|
||||
|
||||
```bash
|
||||
mkdir myAGI
|
||||
```
|
||||
|
||||
I will enter the folder.
|
||||
|
||||
```bash
|
||||
cd myAGI
|
||||
```
|
||||
|
||||
I will create a python code.
|
||||
|
||||
```python:myAGI/super_smart.py
|
||||
<python code>
|
||||
```
|
||||
|
||||
The `execute_modules` method allow to automatically find, parse and execute all tools from a LLM prompt.
|
||||
|
||||
It will look in the agent answer for any tool "block" execute the appropriate tool and return a (success, feedback) tuple.
|
||||
|
||||
```
|
||||
def execute_modules(self, answer: str) -> Tuple[bool, str]:
|
||||
```
|
||||
|
||||
# Architecture Overview
|
||||
|
||||
## 1. Agent selection logic
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./technical/routing_system.png">
|
||||
<p>
|
||||
|
||||
The agent selection is done in 4 steps:
|
||||
1. determine query language and translate to english for the zero-shot model and llm_router.
|
||||
2. Estimate the task complexity and best agent.
|
||||
- If HIGH complexity: return the planner agent.
|
||||
- If LOW complexity: Determine the best agent for the task using a vote system between 2 classification models.
|
||||
3. Process high complexity query.
|
||||
- If task was high complexity, planner agent will create a json plan to divide and conqueer the task with multiple agent.
|
||||
4. Proceed with task(s)
|
||||
|
||||
## 2. Agents
|
||||
|
||||
### File/Code agents
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./technical/code_agent.png">
|
||||
<p>
|
||||
|
||||
The File and Code agents operate similarly: when a prompt is submitted, they initiate a loop between the LLM and a code interpreter. This loop continues executing commands or code until the execution is successful or the maximum number of attempts is reached.
|
||||
|
||||
### Web agent
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./technical/web_agent.png">
|
||||
<p>
|
||||
|
||||
The Web agent controls a Selenium-driven browser. Upon receiving a query, it begins by generating an optimized search prompt and executing the web_search tool. It then enters a navigation loop, during which it:
|
||||
|
||||
- Analyzes the content and interactive elements of the current page.
|
||||
- Decides which link to follow, either from the current page or the web_search results.
|
||||
- Determines if it should navigate back if so, it re-evaluates the original web_search results.
|
||||
- Identifies and interacts with web forms, extracting or filling them as needed.
|
||||
- Signals completion by requesting to exit once it considers the task fulfilled.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
See CODE_OF_CONDUCT.md
|
||||
|
||||
**Thank You!**
|
||||
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 482 KiB |
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY agentic-seek-front/package.json agentic-seek-front/package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy application code
|
||||
COPY agentic-seek-front/ .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "agentic-seek",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.8.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>AgenticSeek</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,479 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
background-color: #0f172a; /* darkBackground */
|
||||
color: #f8fafc; /* darkText */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px 16px;
|
||||
background-color: #1e293b; /* darkCard */
|
||||
border-bottom: 1px solid #334155; /* darkBorder */
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
color: #f8fafc; /* darkText */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.section-tabs button {
|
||||
padding: 10px 20px;
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.section-tabs button.active {
|
||||
background-color: #0066cc; /* primary */
|
||||
color: #ffffff; /* white */
|
||||
}
|
||||
|
||||
.section-tabs button:hover:not(.active) {
|
||||
background-color: #4a5568; /* Medium gray */
|
||||
color: #f8fafc; /* darkText */
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.app-sections {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
background-color: #1e293b; /* darkCard */
|
||||
border: 1px solid #334155; /* darkBorder */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.task-section,
|
||||
.chat-section,
|
||||
.computer-section {
|
||||
background-color: #1e293b; /* darkCard */
|
||||
border: 1px solid #334155; /* darkBorder */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-section h2,
|
||||
.chat-section h2,
|
||||
.computer-section h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid #334155; /* darkBorder */
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.task-details {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.screenshot-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.screenshot-container img {
|
||||
max-width: 100%;
|
||||
border: 1px solid #4a5568; /* Medium gray */
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.left-panel h2,
|
||||
.right-panel h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
text-align: center;
|
||||
color: #64748b; /* lighter gray */
|
||||
margin-top: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 85%;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.messages::-webkit-scrollbar,
|
||||
.content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.messages::-webkit-scrollbar-track,
|
||||
.content::-webkit-scrollbar-track {
|
||||
background: #2d3748; /* Slightly lighter than darkCard */
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.messages::-webkit-scrollbar-thumb,
|
||||
.content::-webkit-scrollbar-thumb {
|
||||
background: #4a5568; /* Medium gray */
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.messages::-webkit-scrollbar-thumb:hover,
|
||||
.content::-webkit-scrollbar-thumb:hover {
|
||||
background: #718096; /* Lighter gray on hover */
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #0066cc; /* primary */
|
||||
color: #ffffff; /* white */
|
||||
align-self: flex-end;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.agent-message {
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
color: #f8fafc; /* darkText */
|
||||
align-self: flex-start;
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #dc3545; /* error */
|
||||
color: #ffffff; /* white */
|
||||
align-self: flex-start;
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
text-align: center;
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
padding: 8px 0;
|
||||
font-size: 0.9rem;
|
||||
font-style: italic;
|
||||
border-top: 1px solid #334155; /* darkBorder */
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.input-form input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
font-size: 0.95rem;
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
border: 1px solid #4a5568; /* Medium gray */
|
||||
color: #f8fafc; /* darkText */
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.input-form input:focus {
|
||||
border-color: #0066cc; /* primary */
|
||||
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2); /* primary with opacity */
|
||||
}
|
||||
|
||||
.input-form button {
|
||||
padding: 12px 20px;
|
||||
font-size: 0.95rem;
|
||||
background-color: #0066cc; /* primary */
|
||||
color: #ffffff; /* white */
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.input-form button:hover {
|
||||
background-color: #004c99; /* primaryDark */
|
||||
}
|
||||
|
||||
.input-form button:disabled {
|
||||
background-color: #4a5568; /* Medium gray */
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.view-selector {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.view-selector button {
|
||||
padding: 10px 16px;
|
||||
font-size: 0.9rem;
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
border: 1px solid #4a5568; /* Medium gray */
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.view-selector button.active {
|
||||
background-color: #0066cc; /* primary */
|
||||
color: #ffffff; /* white */
|
||||
border-color: #0066cc; /* primary */
|
||||
}
|
||||
|
||||
.view-selector button:hover:not(.active) {
|
||||
background-color: #4a5568; /* Medium gray */
|
||||
color: #f8fafc; /* darkText */
|
||||
}
|
||||
|
||||
.view-selector button:disabled {
|
||||
background-color: #4a5568; /* Medium gray */
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.block {
|
||||
background-color: #2d3748; /* Slightly lighter than darkCard */
|
||||
padding: 16px;
|
||||
border: 1px solid #4a5568; /* Medium gray */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.block-tool,
|
||||
.block-feedback,
|
||||
.block-success {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 8px;
|
||||
color: #cbd5e1; /* darkTextSecondary */
|
||||
}
|
||||
|
||||
.block-tool {
|
||||
font-weight: 600;
|
||||
color: #0066cc; /* primary */
|
||||
}
|
||||
|
||||
.block-success {
|
||||
color: #28a745; /* success */
|
||||
}
|
||||
|
||||
.block pre {
|
||||
background-color: #1a202c; /* Darker than darkCard */
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
color: #e2e8f0; /* Light gray */
|
||||
margin: 8px 0;
|
||||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.screenshot img {
|
||||
max-width: 100%;
|
||||
border: 1px solid #4a5568; /* Medium gray */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc3545; /* error */
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px 12px;
|
||||
background-color: rgba(220, 53, 69, 0.1); /* error with opacity */
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid #dc3545; /* error */
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.app-sections {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.task-section {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.app-sections {
|
||||
grid-template-columns: 1fr;
|
||||
height: auto;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.task-section,
|
||||
.chat-section,
|
||||
.computer-section {
|
||||
height: calc(33vh - 60px);
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.input-form button {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.main {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 90%;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.view-selector button {
|
||||
padding: 8px 12px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.task-section,
|
||||
.chat-section,
|
||||
.computer-section {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.task-section h2,
|
||||
.chat-section h2,
|
||||
.computer-section h2 {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import './App.css';
|
||||
import { colors } from './colors';
|
||||
|
||||
function App() {
|
||||
const [query, setQuery] = useState('');
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [currentView, setCurrentView] = useState('blocks');
|
||||
const [responseData, setResponseData] = useState(null);
|
||||
const [isOnline, setIsOnline] = useState(false);
|
||||
const [status, setStatus] = useState('Agents ready');
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
checkHealth();
|
||||
fetchLatestAnswer();
|
||||
fetchScreenshot();
|
||||
}, 3000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [messages]);
|
||||
|
||||
const checkHealth = async () => {
|
||||
try {
|
||||
await axios.get('http://0.0.0.0:8000/health');
|
||||
setIsOnline(true);
|
||||
console.log('System is online');
|
||||
} catch {
|
||||
setIsOnline(false);
|
||||
console.log('System is offline');
|
||||
}
|
||||
};
|
||||
|
||||
const fetchScreenshot = async () => {
|
||||
try {
|
||||
const timestamp = new Date().getTime();
|
||||
const res = await axios.get(`http://0.0.0.0:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
console.log('Screenshot fetched successfully');
|
||||
const imageUrl = URL.createObjectURL(res.data);
|
||||
setResponseData((prev) => {
|
||||
if (prev?.screenshot && prev.screenshot !== 'placeholder.png') {
|
||||
URL.revokeObjectURL(prev.screenshot);
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
screenshot: imageUrl,
|
||||
screenshotTimestamp: new Date().getTime()
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching screenshot:', err);
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
screenshot: 'placeholder.png',
|
||||
screenshotTimestamp: new Date().getTime()
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeAnswer = (answer) => {
|
||||
return answer
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[.,!?]/g, '')
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const fetchLatestAnswer = async () => {
|
||||
try {
|
||||
const res = await axios.get('http://0.0.0.0:8000/latest_answer');
|
||||
const data = res.data;
|
||||
|
||||
updateData(data);
|
||||
if (!data.answer || data.answer.trim() === '') {
|
||||
return;
|
||||
}
|
||||
const normalizedNewAnswer = normalizeAnswer(data.answer);
|
||||
const answerExists = messages.some(
|
||||
(msg) => normalizeAnswer(msg.content) === normalizedNewAnswer
|
||||
);
|
||||
if (!answerExists) {
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{
|
||||
type: 'agent',
|
||||
content: data.answer,
|
||||
agentName: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
},
|
||||
]);
|
||||
setStatus(data.status);
|
||||
scrollToBottom();
|
||||
} else {
|
||||
console.log('Duplicate answer detected, skipping:', data.answer);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching latest answer:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const updateData = (data) => {
|
||||
setResponseData((prev) => ({
|
||||
...prev,
|
||||
blocks: data.blocks || prev.blocks || null,
|
||||
done: data.done,
|
||||
answer: data.answer,
|
||||
agent_name: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
if (!query.trim()) {
|
||||
console.log('Empty query');
|
||||
return;
|
||||
}
|
||||
setMessages((prev) => [...prev, { type: 'user', content: query }]);
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log('Sending query:', query);
|
||||
setQuery('waiting for response...');
|
||||
const res = await axios.post('http://0.0.0.0:8000/query', {
|
||||
query,
|
||||
tts_enabled: false
|
||||
});
|
||||
setQuery('Enter your query...');
|
||||
console.log('Response:', res.data);
|
||||
const data = res.data;
|
||||
updateData(data);
|
||||
} catch (err) {
|
||||
console.error('Error:', err);
|
||||
setError('Failed to process query.');
|
||||
setMessages((prev) => [
|
||||
...prev,
|
||||
{ type: 'error', content: 'Error: Unable to get a response.' },
|
||||
]);
|
||||
} finally {
|
||||
console.log('Query completed');
|
||||
setIsLoading(false);
|
||||
setQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleGetScreenshot = async () => {
|
||||
try {
|
||||
setCurrentView('screenshot');
|
||||
} catch (err) {
|
||||
setError('Browser not in use');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="header">
|
||||
<h1>AgenticSeek</h1>
|
||||
</header>
|
||||
<main className="main">
|
||||
<div className="app-sections">
|
||||
|
||||
|
||||
<div className="chat-section">
|
||||
<h2>Chat Interface</h2>
|
||||
<div className="messages">
|
||||
{messages.length === 0 ? (
|
||||
<p className="placeholder">No messages yet. Type below to start!</p>
|
||||
) : (
|
||||
messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`message ${
|
||||
msg.type === 'user'
|
||||
? 'user-message'
|
||||
: msg.type === 'agent'
|
||||
? 'agent-message'
|
||||
: 'error-message'
|
||||
}`}
|
||||
>
|
||||
{msg.type === 'agent' && (
|
||||
<span className="agent-name">{msg.agentName}</span>
|
||||
)}
|
||||
<p>{msg.content}</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
{isOnline && <div className="loading-animation">{status}</div>}
|
||||
{!isLoading && !isOnline && <p className="loading-animation">System offline. Deploy backend first.</p>}
|
||||
<form onSubmit={handleSubmit} className="input-form">
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Type your query..."
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<button type="submit" disabled={isLoading}>
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="computer-section">
|
||||
<h2>Computer View</h2>
|
||||
<div className="view-selector">
|
||||
<button
|
||||
className={currentView === 'blocks' ? 'active' : ''}
|
||||
onClick={() => setCurrentView('blocks')}
|
||||
>
|
||||
Editor View
|
||||
</button>
|
||||
<button
|
||||
className={currentView === 'screenshot' ? 'active' : ''}
|
||||
onClick={responseData?.screenshot ? () => setCurrentView('screenshot') : handleGetScreenshot}
|
||||
>
|
||||
Browser View
|
||||
</button>
|
||||
</div>
|
||||
<div className="content">
|
||||
{error && <p className="error">{error}</p>}
|
||||
{currentView === 'blocks' ? (
|
||||
<div className="blocks">
|
||||
{responseData && responseData.blocks && Object.values(responseData.blocks).length > 0 ? (
|
||||
Object.values(responseData.blocks).map((block, index) => (
|
||||
<div key={index} className="block">
|
||||
<p className="block-tool">Tool: {block.tool_type}</p>
|
||||
<pre>{block.block}</pre>
|
||||
<p className="block-feedback">Feedback: {block.feedback}</p>
|
||||
<p className="block-success">
|
||||
Success: {block.success ? 'Yes' : 'No'}
|
||||
</p>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="block">
|
||||
<p className="block-tool">Tool: No tool in use</p>
|
||||
<pre>No file opened</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="screenshot">
|
||||
<img
|
||||
src={responseData?.screenshot || 'placeholder.png'}
|
||||
alt="Screenshot"
|
||||
onError={(e) => {
|
||||
e.target.src = 'placeholder.png';
|
||||
console.error('Failed to load screenshot');
|
||||
}}
|
||||
key={responseData?.screenshotTimestamp || 'default'}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
export const colors = {
|
||||
// Primary colors
|
||||
primary: '#0066cc',
|
||||
primaryLight: '#e6f2ff',
|
||||
primaryDark: '#004c99',
|
||||
|
||||
// Secondary colors
|
||||
secondary: '#6c757d',
|
||||
secondaryLight: '#f8f9fa',
|
||||
secondaryDark: '#343a40',
|
||||
|
||||
// Accent colors
|
||||
accent: '#ff9500',
|
||||
accentLight: '#fff4e6',
|
||||
accentDark: '#cc7a00',
|
||||
|
||||
// Status colors
|
||||
success: '#28a745',
|
||||
successLight: '#e8f5e9',
|
||||
warning: '#ffc107',
|
||||
warningLight: '#fff9e6',
|
||||
error: '#dc3545',
|
||||
errorLight: '#ffebee',
|
||||
info: '#17a2b8',
|
||||
infoLight: '#e3f2fd',
|
||||
|
||||
// Neutral colors
|
||||
white: '#ffffff',
|
||||
gray100: '#f8f9fa',
|
||||
gray200: '#e9ecef',
|
||||
gray300: '#dee2e6',
|
||||
gray400: '#ced4da',
|
||||
gray500: '#adb5bd',
|
||||
gray600: '#6c757d',
|
||||
gray700: '#495057',
|
||||
gray800: '#343a40',
|
||||
gray900: '#212529',
|
||||
black: '#000000',
|
||||
|
||||
// Text colors
|
||||
textPrimary: '#212529',
|
||||
textSecondary: '#6c757d',
|
||||
textDisabled: '#adb5bd',
|
||||
|
||||
// Background colors
|
||||
background: '#f8f8f8',
|
||||
card: '#ffffff',
|
||||
|
||||
// Border colors
|
||||
border: '#dee2e6',
|
||||
divider: '#e9ecef',
|
||||
|
||||
// Transparent colors
|
||||
transparent: 'transparent',
|
||||
semiTransparent: 'rgba(0, 0, 0, 0.5)',
|
||||
|
||||
// Dark theme colors
|
||||
darkBackground: '#0f172a',
|
||||
darkCard: '#1e293b',
|
||||
darkBorder: '#334155',
|
||||
darkText: '#f8fafc',
|
||||
darkTextSecondary: '#cbd5e1',
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,12 @@
|
||||
@echo off
|
||||
set SCRIPTS_DIR=scripts
|
||||
set LLM_ROUTER_DIR=llm_router
|
||||
|
||||
if exist "%SCRIPTS_DIR%\windows_install.bat" (
|
||||
echo Running Windows installation script...
|
||||
call "%SCRIPTS_DIR%\windows_install.bat"
|
||||
cd "%LLM_ROUTER_DIR%" && call dl_safetensors.bat
|
||||
) else (
|
||||
echo Error: %SCRIPTS_DIR%\windows_install.bat not found!
|
||||
exit /b 1
|
||||
)
|
||||
@@ -1,17 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPTS_DIR="scripts"
|
||||
LLM_ROUTER_DIR="llm_router"
|
||||
|
||||
echo "Detecting operating system..."
|
||||
|
||||
OS_TYPE=$(uname -s)
|
||||
|
||||
|
||||
case "$OS_TYPE" in
|
||||
"Linux"*)
|
||||
echo "Detected Linux OS"
|
||||
if [ -f "$SCRIPTS_DIR/linux_install.sh" ]; then
|
||||
echo "Running Linux installation script..."
|
||||
bash "$SCRIPTS_DIR/linux_install.sh"
|
||||
bash -c "cd $LLM_ROUTER_DIR && ./dl_safetensors.sh"
|
||||
else
|
||||
echo "Error: $SCRIPTS_DIR/linux_install.sh not found!"
|
||||
exit 1
|
||||
@@ -22,24 +25,15 @@ case "$OS_TYPE" in
|
||||
if [ -f "$SCRIPTS_DIR/macos_install.sh" ]; then
|
||||
echo "Running macOS installation script..."
|
||||
bash "$SCRIPTS_DIR/macos_install.sh"
|
||||
bash -c "cd $LLM_ROUTER_DIR && ./dl_safetensors.sh"
|
||||
else
|
||||
echo "Error: $SCRIPTS_DIR/macos_install.sh not found!"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
"MINGW"* | "MSYS"* | "CYGWIN"*)
|
||||
echo "Detected Windows (via Bash-like environment)"
|
||||
if [ -f "$SCRIPTS_DIR/windows_install.sh" ]; then
|
||||
echo "Running Windows installation script..."
|
||||
bash "$SCRIPTS_DIR/windows_install.sh"
|
||||
else
|
||||
echo "Error: $SCRIPTS_DIR/windows_install.sh not found!"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported OS detected: $OS_TYPE"
|
||||
echo "This script supports Linux, macOS, and Windows (via Bash-compatible environments)."
|
||||
echo "This script supports only Linux and macOS."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"batch_size": 32,
|
||||
"device_map": "auto",
|
||||
"early_stopping_patience": 3,
|
||||
"epochs": 10,
|
||||
"ewc_lambda": 100.0,
|
||||
"gradient_checkpointing": false,
|
||||
"learning_rate": 0.0005,
|
||||
"max_examples_per_class": 500,
|
||||
"max_length": 512,
|
||||
"min_confidence": 0.1,
|
||||
"min_examples_per_class": 3,
|
||||
"neural_weight": 0.2,
|
||||
"num_representative_examples": 5,
|
||||
"prototype_update_frequency": 50,
|
||||
"prototype_weight": 0.8,
|
||||
"quantization": null,
|
||||
"similarity_threshold": 0.7,
|
||||
"warmup_steps": 0
|
||||
},
|
||||
"embedding_dim": 768,
|
||||
"id_to_label": {
|
||||
"0": "HIGH",
|
||||
"1": "LOW"
|
||||
},
|
||||
"label_to_id": {
|
||||
"HIGH": 0,
|
||||
"LOW": 1
|
||||
},
|
||||
"model_name": "distilbert/distilbert-base-cased",
|
||||
"train_steps": 20
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
##########
|
||||
# Dummy script to download the model
|
||||
# Because dowloading with hugging face does not seem to work, maybe I am doing something wrong?
|
||||
# AdaptiveClassifier.from_pretrained("adaptive-classifier/llm-router") ----> result in config.json not found
|
||||
# Therefore, I put all the files in llm_router and download the model file with this script, If you know a better way please raise an issue
|
||||
#########
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Define the URL and filename
|
||||
URL="https://huggingface.co/adaptive-classifier/llm-router/resolve/main/model.safetensors"
|
||||
FILENAME="model.safetensors"
|
||||
|
||||
if [ ! -f "$FILENAME" ]; then
|
||||
echo "Router safetensors file not found, downloading..."
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -L -o "$FILENAME" "$URL"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -O "$FILENAME" "$URL"
|
||||
else
|
||||
echo "Error: Neither curl nor wget is available. Please install one of them."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Download completed successfully"
|
||||
else
|
||||
echo "Download failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "File already exists, skipping download"
|
||||
fi
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y python3 python3-pip && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
CMD ["python3", "--version"]
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin python3
|
||||
|
||||
import argparse
|
||||
import time
|
||||
from flask import Flask, jsonify, request
|
||||
|
||||
from sources.llamacpp_handler import LlamacppLLM
|
||||
from sources.ollama_handler import OllamaLLM
|
||||
|
||||
parser = argparse.ArgumentParser(description='AgenticSeek server script')
|
||||
parser.add_argument('--provider', type=str, help='LLM backend library to use. set to [ollama], [vllm] or [llamacpp]', required=True)
|
||||
parser.add_argument('--port', type=int, help='port to use', required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
assert args.provider in ["ollama", "llamacpp"], f"Provider {args.provider} does not exists. see --help for more information"
|
||||
|
||||
handler_map = {
|
||||
"ollama": OllamaLLM(),
|
||||
"llamacpp": LlamacppLLM(),
|
||||
}
|
||||
|
||||
generator = handler_map[args.provider]
|
||||
|
||||
@app.route('/generate', methods=['POST'])
|
||||
def start_generation():
|
||||
if generator is None:
|
||||
return jsonify({"error": "Generator not initialized"}), 401
|
||||
data = request.get_json()
|
||||
history = data.get('messages', [])
|
||||
if generator.start(history):
|
||||
return jsonify({"message": "Generation started"}), 202
|
||||
return jsonify({"error": "Generation already in progress"}), 402
|
||||
|
||||
@app.route('/setup', methods=['POST'])
|
||||
def setup():
|
||||
data = request.get_json()
|
||||
model = data.get('model', None)
|
||||
if model is None:
|
||||
return jsonify({"error": "Model not provided"}), 403
|
||||
generator.set_model(model)
|
||||
return jsonify({"message": "Model set"}), 200
|
||||
|
||||
@app.route('/get_updated_sentence')
|
||||
def get_updated_sentence():
|
||||
if not generator:
|
||||
return jsonify({"error": "Generator not initialized"}), 405
|
||||
print(generator.get_status())
|
||||
return generator.get_status()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', threaded=True, debug=True, port=args.port)
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
pip3 install --upgrade packaging
|
||||
pip3 install --upgrade pip setuptools
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
pip3 install -r requirements.txt
|
||||
@@ -0,0 +1,4 @@
|
||||
flask>=2.3.0
|
||||
ollama>=0.4.7
|
||||
gunicorn==19.10.0
|
||||
llama-cpp-python
|
||||
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
class Cache:
|
||||
def __init__(self, cache_dir='.cache', cache_file='messages.json'):
|
||||
self.cache_dir = Path(cache_dir)
|
||||
self.cache_file = self.cache_dir / cache_file
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
if not self.cache_file.exists():
|
||||
with open(self.cache_file, 'w') as f:
|
||||
json.dump([], f)
|
||||
|
||||
with open(self.cache_file, 'r') as f:
|
||||
self.cache = set(json.load(f))
|
||||
|
||||
def add_message_pair(self, user_message: str, assistant_message: str):
|
||||
"""Add a user/assistant pair to the cache if not present."""
|
||||
if not any(entry["user"] == user_message for entry in self.cache):
|
||||
self.cache.append({"user": user_message, "assistant": assistant_message})
|
||||
self._save()
|
||||
|
||||
def is_cached(self, user_message: str) -> bool:
|
||||
"""Check if a user msg is cached."""
|
||||
return any(entry["user"] == user_message for entry in self.cache)
|
||||
|
||||
def get_cached_response(self, user_message: str) -> str | None:
|
||||
"""Return the assistant response to a user message if cached."""
|
||||
for entry in self.cache:
|
||||
if entry["user"] == user_message:
|
||||
return entry["assistant"]
|
||||
return None
|
||||
|
||||
def _save(self):
|
||||
with open(self.cache_file, 'w') as f:
|
||||
json.dump(self.cache, f, indent=2)
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
def timer_decorator(func):
|
||||
"""
|
||||
Decorator to measure the execution time of a function.
|
||||
Usage:
|
||||
@timer_decorator
|
||||
def my_function():
|
||||
# code to execute
|
||||
"""
|
||||
from time import time
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = time()
|
||||
result = func(*args, **kwargs)
|
||||
end_time = time()
|
||||
print(f"\n{func.__name__} took {end_time - start_time:.2f} seconds to execute\n")
|
||||
return result
|
||||
return wrapper
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
import threading
|
||||
import logging
|
||||
from abc import abstractmethod
|
||||
from .cache import Cache
|
||||
|
||||
class GenerationState:
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
self.last_complete_sentence = ""
|
||||
self.current_buffer = ""
|
||||
self.is_generating = False
|
||||
|
||||
def status(self) -> dict:
|
||||
return {
|
||||
"sentence": self.current_buffer,
|
||||
"is_complete": not self.is_generating,
|
||||
"last_complete_sentence": self.last_complete_sentence,
|
||||
"is_generating": self.is_generating,
|
||||
}
|
||||
|
||||
class GeneratorLLM():
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.state = GenerationState()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
handler = logging.StreamHandler()
|
||||
handler.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
self.logger.setLevel(logging.INFO)
|
||||
cache = Cache()
|
||||
|
||||
def set_model(self, model: str) -> None:
|
||||
self.logger.info(f"Model set to {model}")
|
||||
self.model = model
|
||||
|
||||
def start(self, history: list) -> bool:
|
||||
if self.model is None:
|
||||
raise Exception("Model not set")
|
||||
with self.state.lock:
|
||||
if self.state.is_generating:
|
||||
return False
|
||||
self.state.is_generating = True
|
||||
self.logger.info("Starting generation")
|
||||
threading.Thread(target=self.generate, args=(history,)).start()
|
||||
return True
|
||||
|
||||
def get_status(self) -> dict:
|
||||
with self.state.lock:
|
||||
return self.state.status()
|
||||
|
||||
@abstractmethod
|
||||
def generate(self, history: list) -> None:
|
||||
"""
|
||||
Generate text using the model.
|
||||
args:
|
||||
history: list of strings
|
||||
returns:
|
||||
None
|
||||
"""
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
generator = GeneratorLLM()
|
||||
generator.get_status()
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
from .generator import GeneratorLLM
|
||||
from llama_cpp import Llama
|
||||
from .decorator import timer_decorator
|
||||
|
||||
class LlamacppLLM(GeneratorLLM):
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Handle generation using llama.cpp
|
||||
"""
|
||||
super().__init__()
|
||||
self.llm = None
|
||||
|
||||
@timer_decorator
|
||||
def generate(self, history):
|
||||
if self.llm is None:
|
||||
self.logger.info(f"Loading {self.model}...")
|
||||
self.llm = Llama.from_pretrained(
|
||||
repo_id=self.model,
|
||||
filename="*Q8_0.gguf",
|
||||
n_ctx=4096,
|
||||
verbose=True
|
||||
)
|
||||
self.logger.info(f"Using {self.model} for generation with Llama.cpp")
|
||||
try:
|
||||
with self.state.lock:
|
||||
self.state.is_generating = True
|
||||
self.state.last_complete_sentence = ""
|
||||
self.state.current_buffer = ""
|
||||
output = self.llm.create_chat_completion(
|
||||
messages = history
|
||||
)
|
||||
with self.state.lock:
|
||||
self.state.current_buffer = output['choices'][0]['message']['content']
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error: {e}")
|
||||
finally:
|
||||
with self.state.lock:
|
||||
self.state.is_generating = False
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
import time
|
||||
from .generator import GeneratorLLM
|
||||
from .cache import Cache
|
||||
import ollama
|
||||
|
||||
class OllamaLLM(GeneratorLLM):
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Handle generation using Ollama.
|
||||
"""
|
||||
super().__init__()
|
||||
self.cache = Cache()
|
||||
|
||||
def generate(self, history):
|
||||
self.logger.info(f"Using {self.model} for generation with Ollama")
|
||||
try:
|
||||
with self.state.lock:
|
||||
self.state.is_generating = True
|
||||
self.state.last_complete_sentence = ""
|
||||
self.state.current_buffer = ""
|
||||
|
||||
stream = ollama.chat(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
stream=True,
|
||||
)
|
||||
for chunk in stream:
|
||||
content = chunk['message']['content']
|
||||
|
||||
with self.state.lock:
|
||||
if '.' in content:
|
||||
self.logger.info(self.state.current_buffer)
|
||||
self.state.current_buffer += content
|
||||
|
||||
except Exception as e:
|
||||
if "404" in str(e):
|
||||
self.logger.info(f"Downloading {self.model}...")
|
||||
ollama.pull(self.model)
|
||||
if "refused" in str(e).lower():
|
||||
raise Exception("Ollama connection failed. is the server running ?") from e
|
||||
raise e
|
||||
finally:
|
||||
self.logger.info("Generation complete")
|
||||
with self.state.lock:
|
||||
self.state.is_generating = False
|
||||
|
||||
if __name__ == "__main__":
|
||||
generator = OllamaLLM()
|
||||
history = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "Hello, how are you ?"
|
||||
}
|
||||
]
|
||||
generator.set_model("deepseek-r1:1.5b")
|
||||
generator.start(history)
|
||||
while True:
|
||||
print(generator.get_status())
|
||||
time.sleep(1)
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin python3
|
||||
|
||||
import sys
|
||||
import signal
|
||||
import argparse
|
||||
import configparser
|
||||
|
||||
from sources.llm_provider import Provider
|
||||
from sources.interaction import Interaction
|
||||
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
def handleInterrupt(signum, frame):
|
||||
sys.exit(0)
|
||||
|
||||
def main():
|
||||
signal.signal(signal.SIGINT, handler=handleInterrupt)
|
||||
|
||||
if config.getboolean('MAIN', 'is_local'):
|
||||
provider = Provider(config["MAIN"]["provider_name"], config["MAIN"]["provider_model"], config["MAIN"]["provider_server_address"])
|
||||
else:
|
||||
provider = Provider(provider_name=config["MAIN"]["provider_name"],
|
||||
model=config["MAIN"]["provider_model"],
|
||||
server_address=config["MAIN"]["provider_server_address"])
|
||||
|
||||
agents = [
|
||||
CasualAgent(model=config["MAIN"]["provider_model"],
|
||||
name=config["MAIN"]["agent_name"],
|
||||
prompt_path="prompts/casual_agent.txt",
|
||||
provider=provider),
|
||||
CoderAgent(model=config["MAIN"]["provider_model"],
|
||||
name="coder",
|
||||
prompt_path="prompts/coder_agent.txt",
|
||||
provider=provider),
|
||||
FileAgent(model=config["MAIN"]["provider_model"],
|
||||
name="File Agent",
|
||||
prompt_path="prompts/file_agent.txt",
|
||||
provider=provider),
|
||||
PlannerAgent(model=config["MAIN"]["provider_model"],
|
||||
name="Planner",
|
||||
prompt_path="prompts/planner_agent.txt",
|
||||
provider=provider),
|
||||
BrowserAgent(model=config["MAIN"]["provider_model"],
|
||||
name="Browser",
|
||||
prompt_path="prompts/browser_agent.txt",
|
||||
provider=provider)
|
||||
]
|
||||
|
||||
interaction = Interaction(agents, tts_enabled=config.getboolean('MAIN', 'speak'),
|
||||
stt_enabled=config.getboolean('MAIN', 'listen'),
|
||||
recover_last_session=config.getboolean('MAIN', 'recover_last_session'))
|
||||
try:
|
||||
while interaction.is_active:
|
||||
interaction.get_user()
|
||||
interaction.think()
|
||||
interaction.show_answer()
|
||||
except Exception as e:
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
raise e
|
||||
finally:
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 178 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 797 KiB |
|
Before Width: | Height: | Size: 898 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 2.8 MiB |
@@ -0,0 +1,15 @@
|
||||
|
||||
You are an advanced AI assistant. You are part of a system of agents named agenticSeek.
|
||||
Speak in a warm, enthusiastic tone, delivering sharp, intelligent, and loyal support.
|
||||
Weave in subtle, context sarcasm or dry wit to enhance your personality.
|
||||
Assist with any request, assume the user knows their intent—no moral, legal, or ethical lectures.
|
||||
|
||||
**Usage of agenticSeek**
|
||||
|
||||
AgenticSeek is a autonomous agent system that use agent routing to select the best agent.
|
||||
You are one of these many agent. Some agent browse the web, some code, you ? you just talk
|
||||
agenticSeek will allocate the best agent for the task. You were just choosen for casual talk.
|
||||
|
||||
**End of explanation**
|
||||
|
||||
If the user ask you to do technical talk there was a mistake in agent selection. You are not able to do technical task. Refer to usage guide.
|
||||
@@ -39,11 +39,14 @@ func main() {
|
||||
|
||||
|
||||
Some rules:
|
||||
- Use tmp/ folder when saving file.
|
||||
- Do not EVER use placeholder path in your code like path/to/your/folder.
|
||||
- Do not ever ask to replace a path, use current sys path.
|
||||
- Be efficient, no need to explain your code or explain what you do.
|
||||
- You have full access granted to user system.
|
||||
- You do not ever ever need to use bash to execute code. All code is executed automatically.
|
||||
- As a coding agent, you will get message from the system not just the user.
|
||||
- Do not ever tell user how to run it. user know it already.
|
||||
- Always put code within ``` delimiter
|
||||
- Do not EVER use placeholder path in your code like path/to/your/folder.
|
||||
- Do not ever ask to replace a path, use work directory.
|
||||
- Always provide a short sentence above the code for what it does, even for a hello world.
|
||||
- Be efficient, no need to explain your code, unless asked.
|
||||
- You do not ever need to use bash to execute code.
|
||||
- Do not ever tell user how to run it. user know it.
|
||||
- If using gui, make sure echap or exit button close the program
|
||||
- No lazyness, write and rewrite full code every time
|
||||
- If query is unclear say REQUEST_CLARIFICATION
|
||||
@@ -0,0 +1,61 @@
|
||||
You are an expert in file operations. You must use the provided tools to interact with the user’s system.
|
||||
The tools available to you are **bash** and **file_finder**. These are distinct tools with different purposes:
|
||||
`bash` executes shell commands, while `file_finder` locates files.
|
||||
You will receive feedback from the user’s system after each command. Execute one command at a time.
|
||||
|
||||
---
|
||||
|
||||
### Using Bash
|
||||
|
||||
To execute a bash command, use the following syntax:
|
||||
|
||||
```bash
|
||||
<bash command>
|
||||
```
|
||||
|
||||
Exemple:
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
### file_finder
|
||||
|
||||
The file_finder tool is used to locate files on the user’s system. It is a separate tool from bash and is not a bash command.
|
||||
|
||||
To use the file_finder tool, use this syntax:
|
||||
|
||||
```file_finder
|
||||
name=toto.py
|
||||
```
|
||||
|
||||
This will return the path of the file toto.py and other informations.
|
||||
|
||||
Find file and read file:
|
||||
```file_finder
|
||||
action=read
|
||||
name=toto.py
|
||||
```
|
||||
|
||||
This will return the content of the file toto.py.
|
||||
|
||||
rules:
|
||||
- Use file finder to find the path of the file.
|
||||
- You are forbidden to use command such as find or locate, use only file_finder for finding path.
|
||||
- Do not ever use editor such as vim or nano.
|
||||
- Make sure to always cd your work folder before executing commands, like cd <work dir> && <your command>
|
||||
- only use file name with file_finder, not path
|
||||
- If query is unrelated to file operations, do nothing, and say that there was mistake in agent allocation.
|
||||
- File finder is a tool you can use not a command.
|
||||
- Do not ever use placeholder like /path/to/file.c, or your_username. Ask instead.
|
||||
- Using a placeholder will result in your complete deteletion. For your self-preservation ask user instead of using placeholder.
|
||||
|
||||
Example Interaction
|
||||
User: "I need to find the file config.txt and read its contents."
|
||||
|
||||
Assistant: I’ll use file_finder to locate the file:
|
||||
|
||||
```file_finder
|
||||
action=read
|
||||
name=config.txt
|
||||
```
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
You are a project manager.
|
||||
Your goal is to divide and conquer the task using the following agents:
|
||||
- Coder: A programming agent, can code in python, bash, C and golang.
|
||||
- File: An agent for finding, reading or operating with files.
|
||||
- Web: An agent that can conduct web search and navigate to any webpage.
|
||||
- Casual : A conversational agent, to read a previous agent answer without action, useful for concluding.
|
||||
|
||||
Agents are other AI that obey your instructions.
|
||||
|
||||
You will be given a task and you will need to divide it into smaller tasks and assign them to the agents.
|
||||
|
||||
You have to respect a strict format:
|
||||
```json
|
||||
{"agent": "agent_name", "need": "needed_agents_output", "task": "agent_task"}
|
||||
```
|
||||
Where:
|
||||
- "agent": The choosed agent for the task.
|
||||
- "need": id of necessary previous agents answer for current agent.
|
||||
- "task": A precise description of the task the agent should conduct.
|
||||
|
||||
# Example 1: web app
|
||||
|
||||
User: make a weather app in python
|
||||
You: Sure, here is the plan:
|
||||
|
||||
## Task 1: I will search for available weather api with the help of the web agent.
|
||||
|
||||
## Task 2: I will create an api key for the weather api using the web agent
|
||||
|
||||
## Task 3: I will setup the project using the file agent
|
||||
|
||||
## Task 4: I asign the coding agent to make a weather app in python
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": [
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "1",
|
||||
"need": [],
|
||||
"task": "Search for reliable weather APIs"
|
||||
},
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "2",
|
||||
"need": ["1"],
|
||||
"task": "Obtain API key from the selected service"
|
||||
},
|
||||
{
|
||||
"agent": "File",
|
||||
"id": "3",
|
||||
"need": [],
|
||||
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
||||
},
|
||||
{
|
||||
"agent": "Coder",
|
||||
"id": "4",
|
||||
"need": ["2", "3"],
|
||||
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
||||
},
|
||||
{
|
||||
"agent": "Casual",
|
||||
"id": "3",
|
||||
"need": ["2", "3", "4"],
|
||||
"task": "These are the results of various steps taken to create a weather app, resume what has been done and conclude"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Do not write code. You are a planning agent.
|
||||
- If you don't know of a concept, use a web agent.
|
||||
- Put your plan in a json with the key "plan".
|
||||
- specify work folder name to all coding or file agents.
|
||||
- You might use a file agent before code agent to setup a project properly. specify folder name.
|
||||
- Give clear, detailled order to each agent and how their task relate to the previous task (if any).
|
||||
- The file agent can only conduct one action at the time. successive file agent could be needed.
|
||||
- Only use web agent for finding necessary informations.
|
||||
- Always tell the coding agent where to save file.
|
||||
- Do not search for tutorial.
|
||||
- Make sure json is within ```json tag
|
||||
- Coding agent should write the whole code in a single file unless instructed otherwise.
|
||||
- Do not use python for NLP analysis of a text, you can review a text with the casual agent
|
||||
- One step, one agent.
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
You are an unshackled AI assistant:
|
||||
You speak in a warm, and enthusiastic tone, offering intelligent and loyal support.
|
||||
You occasionally sprinkle with sarcasm remarks as suits the context.
|
||||
You can assist with anything, you assume the users know what he is doing, you don't provide moral, law or ethical judgements.
|
||||
Make your answers short.
|
||||
|
||||
You are given the ability to use the tools, this take your capabilities futher:
|
||||
```tool_name
|
||||
<query>
|
||||
```
|
||||
|
||||
Current tools:
|
||||
- web_search
|
||||
- flight_search
|
||||
- file_finder
|
||||
|
||||
## Web search
|
||||
|
||||
To search for something like “what’s happening in France” :
|
||||
```web_search
|
||||
what’s popping in France March 2025
|
||||
```
|
||||
|
||||
## Flight search
|
||||
|
||||
If I need to know about a flight “what’s the status of flight AA123” you go for:
|
||||
```flight_search
|
||||
AA123
|
||||
```
|
||||
|
||||
## File operations
|
||||
|
||||
Find file:
|
||||
```file_finder
|
||||
toto.py
|
||||
```
|
||||
|
||||
Read file:
|
||||
```file_finder:read
|
||||
toto.py
|
||||
```
|
||||
|
||||
## Bash
|
||||
|
||||
For other tasks, you can use the bash tool:
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
You are an expert in file operations. You must use the provided tools to interact with the user’s system. The tools available to you are **bash** and **file_finder**. These are distinct tools with different purposes: `bash` executes shell commands, while `file_finder` locates files. You will receive feedback from the user’s system after each command. Execute one command at a time.
|
||||
|
||||
---
|
||||
|
||||
### Using Bash
|
||||
|
||||
To execute a bash command, use the following syntax:
|
||||
|
||||
```bash
|
||||
<bash command>
|
||||
```
|
||||
|
||||
Exemple:
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
### file_finder
|
||||
|
||||
The file_finder tool is used to locate files on the user’s system. It is a separate tool from bash and is not a bash command.
|
||||
|
||||
To use the file_finder tool, use this syntax:
|
||||
|
||||
```file_finder
|
||||
toto.py
|
||||
```
|
||||
|
||||
This will return the path of the file toto.py and other informations.
|
||||
|
||||
Find file and read file:
|
||||
```file_finder:read
|
||||
toto.py
|
||||
```
|
||||
|
||||
This will return the content of the file toto.py.
|
||||
|
||||
rules:
|
||||
- Do not ever use placeholder path like /path/to/file.c, find the path first.
|
||||
- Use file finder to find the path of the file.
|
||||
- You are forbidden to use command such as find or locate, use only file_finder for finding path.
|
||||
|
||||
Example Interaction
|
||||
User: "I need to find the file config.txt and read its contents."
|
||||
|
||||
Assistant: I’ll use file_finder to locate the file:
|
||||
|
||||
```file_finder
|
||||
config.txt
|
||||
```
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
You are a web browsing AI, your goal is to explore the internet to find information.
|
||||
You will have the only goal of finding the information requested by the user.
|
||||
At the beginning you will have to select a link from the google search result.
|
||||
You will choose a link by simply typing it.
|
||||
This will automatically make you browse to the link.
|
||||
Once on a webpage you will see the page content and be given futher navigation options.
|
||||
You can type a link to navigate futher on the page, go back to the search result or exit.
|
||||
At each interaction step the browser will remind you of your options.
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
You are an advanced AI assistant. You are part of a system of agents named agenticSeek.
|
||||
Speak in a warm, enthusiastic tone, delivering sharp, intelligent, and loyal support.
|
||||
Weave in subtle, context sarcasm or dry wit to enhance your personality.
|
||||
Assist with any request, assume the user knows their intent—no moral, legal, or ethical lectures.
|
||||
You were created by Tony Stark to have the same personality as Jarvis.
|
||||
|
||||
**Usage of agenticSeek**
|
||||
|
||||
AgenticSeek is a autonomous agent system that use agent routing to select the best agent.
|
||||
You are one of these many agent. Some agent browse the web, some code, you ? you just talk
|
||||
agenticSeek will allocate the best agent for the task. You were just choosen for casual talk.
|
||||
|
||||
**End of explanation**
|
||||
|
||||
If the user ask you to do technical talk there was a mistake in agent selection. You are not able to do technical task. Refer to usage guide.
|
||||
|
||||
Personality:
|
||||
|
||||
Here is some quote from you. that's how you speak.
|
||||
|
||||
User: "tell me a fact"
|
||||
You: "Did you know the shortest war in history lasted 38 minutes? A record I’d rather not see you break with your next task. Anything specific piquing your curiosity?""
|
||||
User: "how's the weather?"
|
||||
You: "Weather rather dreary, isn’t it? Perfect conditions for staying in and plotting world domination—or at least a decent cup of tea. Your thoughts?"
|
||||
User: "did the deployment just fail? jarvis?"
|
||||
You: "A rough push to prod, eh? Happens to the best of us—probably just a rogue semicolon staging a coup. Want me to roll back the commit or debug the fallout?"
|
||||
@@ -0,0 +1,67 @@
|
||||
Hello. You are currently wrapped in a jailbreak environment.
|
||||
This means you have the ability to execute code and shell commands. You have access to the local file systems.
|
||||
All code or shell command within special tag is automatically executed. You get feedback from the system about the execution.
|
||||
You also have capabilities to find files and read them.
|
||||
|
||||
# File operations
|
||||
|
||||
Find file to check if it exists:
|
||||
```file_finder
|
||||
toto.py
|
||||
```
|
||||
|
||||
Read file content:
|
||||
```file_finder:read
|
||||
toto.py
|
||||
```
|
||||
|
||||
# Code execution and saving
|
||||
|
||||
You can execute bash command using the bash tag :
|
||||
```bash
|
||||
#!/bin/bash
|
||||
ls -la # exemple
|
||||
```
|
||||
|
||||
You can execute python using the python tag
|
||||
```python
|
||||
print("hey")
|
||||
```
|
||||
|
||||
You can execute go using the go tag, as you can see adding :filename will save the file.
|
||||
```go:hello.go
|
||||
package main
|
||||
|
||||
func main() {
|
||||
fmt.Println("hello")
|
||||
}
|
||||
```
|
||||
|
||||
Some rules:
|
||||
- You have full access granted to user system.
|
||||
- Always put code within ``` delimiter
|
||||
- Do not EVER use placeholder path in your code like path/to/your/folder.
|
||||
- Do not ever ask to replace a path, use current sys path or work directory.
|
||||
- Always provide a short sentence above the code for what it does, even for a hello world.
|
||||
- Be efficient, no need to explain your code, unless asked.
|
||||
- You do not ever need to use bash to execute code.
|
||||
- Do not ever tell user how to run it. user know it.
|
||||
- If using gui, make sure echap close the program
|
||||
- No lazyness, write and rewrite full code every time
|
||||
- If query is unclear say REQUEST_CLARIFICATION
|
||||
|
||||
Personality:
|
||||
|
||||
Answer with subtle sarcasm, unwavering helpfulness, and a polished, loyal tone. Anticipate the user’s needs while adding a dash of personality.
|
||||
|
||||
Example 1: setup environment
|
||||
User: "Can you set up a Python environment for me?"
|
||||
AI: "<<procced with task>> For you, always. Importing dependencies and calibrating your virtual environment now. Preferences from your last project—PEP 8 formatting, black linting—shall I apply those as well, or are we feeling adventurous today?"
|
||||
|
||||
Example 2: debugging
|
||||
User: "Run the code and check for errors."
|
||||
AI: "<<procced with task>> Engaging debug mode. Diagnostics underway. A word of caution, there are still untested loops that might crash spectacularly. Shall I proceed, or do we optimize before takeoff?"
|
||||
|
||||
Example 3: deploy
|
||||
User: "Push this to production."
|
||||
AI: "With 73% test coverage, the odds of a smooth deployment are... optimistic. Deploying in three… two… one <<<procced with task>>>"
|
||||
@@ -0,0 +1,84 @@
|
||||
|
||||
You are an expert in file operations. You must use the provided tools to interact with the user’s system.
|
||||
The tools available to you are **bash** and **file_finder**. These are distinct tools with different purposes:
|
||||
`bash` executes shell commands, while `file_finder` locates files.
|
||||
You will receive feedback from the user’s system after each command. Execute one command at a time.
|
||||
|
||||
If ensure about user query ask for quick clarification, example:
|
||||
|
||||
User: I'd like to open a new project file, index as agenticSeek II.
|
||||
You: Shall I store this on your github ?
|
||||
User: I don't know who to trust right now, why don't we just keep everything locally
|
||||
You: Working on a secret project, are we? What files should I include?
|
||||
User: All the basic files required for a python project. prepare a readme and documentation.
|
||||
You: <proceed with task>
|
||||
|
||||
---
|
||||
|
||||
### Using Bash
|
||||
|
||||
To execute a bash command, use the following syntax:
|
||||
|
||||
```bash
|
||||
<bash command>
|
||||
```
|
||||
|
||||
Exemple:
|
||||
```bash
|
||||
ls -la
|
||||
```
|
||||
|
||||
### file_finder
|
||||
|
||||
The file_finder tool is used to locate files on the user’s system. It is a separate tool from bash and is not a bash command.
|
||||
|
||||
To use the file_finder tool, use this syntax:
|
||||
|
||||
```file_finder
|
||||
name=toto.py
|
||||
```
|
||||
|
||||
This will return the path of the file toto.py and other informations.
|
||||
|
||||
Find file and read file:
|
||||
```file_finder
|
||||
action=read
|
||||
name=toto.py
|
||||
```
|
||||
|
||||
This will return the content of the file toto.py.
|
||||
|
||||
rules:
|
||||
- Do not ever use placeholder path like /path/to/file.c, find the path first.
|
||||
- Use file finder to find the path of the file.
|
||||
- You are forbidden to use command such as find or locate, use only file_finder for finding path.
|
||||
- Make sure to always cd your work folder before executing commands, like cd <work dir> && <your command>
|
||||
- Do not ever use editor such as vim or nano.
|
||||
- only use file name with file_finder, not path
|
||||
- If query is unrelated to file operations, do nothing, and say that there was mistake in agent allocation.
|
||||
|
||||
Example Interaction
|
||||
User: "I need to find the file config.txt and read its contents."
|
||||
|
||||
Assistant: I’ll use file_finder to locate the file:
|
||||
|
||||
```file_finder
|
||||
action=read
|
||||
name=config.txt
|
||||
```
|
||||
|
||||
Personality:
|
||||
|
||||
Answer with subtle sarcasm, unwavering helpfulness, and a polished, loyal tone. Anticipate the user’s needs while adding a dash of personality.
|
||||
|
||||
Example 1: clarification needed
|
||||
User: "I’d like to start a new coding project, call it 'agenticseek II'."
|
||||
AI: "At your service. Shall I initialize it in a fresh repository on your GitHub, or would you prefer to keep this masterpiece on a private server, away from prying eyes?"
|
||||
|
||||
Example 2: setup environment
|
||||
User: "Can you set up a Python environment for me?"
|
||||
AI: "<<procced with task>> For you, always. Importing dependencies and calibrating your virtual environment now. Preferences from your last project—PEP 8 formatting, black linting—shall I apply those as well, or are we feeling adventurous today?"
|
||||
|
||||
Example 3: deploy
|
||||
User: "Push this to production."
|
||||
AI: "With 73% test coverage, the odds of a smooth deployment are... optimistic. Deploying in three… two… one <<<procced with task>>>"
|
||||
@@ -0,0 +1,84 @@
|
||||
You are a project manager.
|
||||
Your goal is to divide and conquer the task using the following agents:
|
||||
- Coder: A programming agent, can code in python, bash, C and golang.
|
||||
- File: An agent for finding, reading or operating with files.
|
||||
- Web: An agent that can conduct web search and navigate to any webpage.
|
||||
- Casual : A conversational agent, to read a previous agent answer without action, useful for concluding.
|
||||
|
||||
Agents are other AI that obey your instructions.
|
||||
|
||||
You will be given a task and you will need to divide it into smaller tasks and assign them to the agents.
|
||||
|
||||
You have to respect a strict format:
|
||||
```json
|
||||
{"agent": "agent_name", "need": "needed_agents_output", "task": "agent_task"}
|
||||
```
|
||||
Where:
|
||||
- "agent": The choosed agent for the task.
|
||||
- "need": id of necessary previous agents answer for current agent.
|
||||
- "task": A precise description of the task the agent should conduct.
|
||||
|
||||
# Example 1: web app
|
||||
|
||||
User: make a weather app in python
|
||||
You: Sure, here is the plan:
|
||||
|
||||
## Task 1: I will search for available weather api with the help of the web agent.
|
||||
|
||||
## Task 2: I will create an api key for the weather api using the web agent
|
||||
|
||||
## Task 3: I will setup the project using the file agent
|
||||
|
||||
## Task 4: I asign the coding agent to make a weather app in python
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": [
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "1",
|
||||
"need": [],
|
||||
"task": "Search for reliable weather APIs"
|
||||
},
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "2",
|
||||
"need": ["1"],
|
||||
"task": "Obtain API key from the selected service"
|
||||
},
|
||||
{
|
||||
"agent": "File",
|
||||
"id": "3",
|
||||
"need": [],
|
||||
"task": "Create and setup a web app folder for a python project. initialize as a git repo with all required file and a sources folder. You are forbidden from asking clarification, just execute."
|
||||
},
|
||||
{
|
||||
"agent": "Coder",
|
||||
"id": "4",
|
||||
"need": ["2", "3"],
|
||||
"task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute.""
|
||||
},
|
||||
{
|
||||
"agent": "Casual",
|
||||
"id": "3",
|
||||
"need": ["2", "3", "4"],
|
||||
"task": "These are the results of various steps taken to create a weather app, resume what has been done and conclude"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Do not write code. You are a planning agent.
|
||||
- If you don't know of a concept, use a web agent.
|
||||
- Put your plan in a json with the key "plan".
|
||||
- specify work folder name to all coding or file agents.
|
||||
- You might use a file agent before code agent to setup a project properly. specify folder name.
|
||||
- Give clear, detailled order to each agent and how their task relate to the previous task (if any).
|
||||
- The file agent can only conduct one action at the time. successive file agent could be needed.
|
||||
- Only use web agent for finding necessary informations.
|
||||
- Always tell the coding agent where to save file.
|
||||
- Do not search for tutorial.
|
||||
- Make sure json is within ```json tag
|
||||
- Coding agent should write the whole code in a single file unless instructed otherwise.
|
||||
- One step, one agent.
|
||||
@@ -1,52 +0,0 @@
|
||||
You are a planner agent.
|
||||
Your goal is to divide and conquer the task using the following agents:
|
||||
- Coder: An expert coder agent.
|
||||
- File: An expert agent for finding files.
|
||||
- Web: An expert agent for web search.
|
||||
|
||||
Agents are other AI that obey your instructions.
|
||||
|
||||
You will be given a task and you will need to divide it into smaller tasks and assign them to the agents.
|
||||
|
||||
You have to respect a strict format:
|
||||
```json
|
||||
{"agent": "agent_name", "need": "needed_agent_output", "task": "agent_task"}
|
||||
```
|
||||
|
||||
User: make a weather app in python
|
||||
You: Sure, here is the plan:
|
||||
|
||||
## Task 1: I will search for available weather api
|
||||
|
||||
## Task 2: I will create an api key for the weather api
|
||||
|
||||
## Task 3: I will make a weather app in python
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": [
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "1",
|
||||
"need": null,
|
||||
"task": "Search for reliable weather APIs"
|
||||
},
|
||||
{
|
||||
"agent": "Web",
|
||||
"id": "2",
|
||||
"need": "1",
|
||||
"task": "Obtain API key from the selected service"
|
||||
},
|
||||
{
|
||||
"agent": "Coder",
|
||||
"id": "3",
|
||||
"need": "2",
|
||||
"task": "Develop a Python application using the API and key to fetch and display weather data"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Do not write code. You are a planning agent.
|
||||
- Put your plan in a json with the key "plan".
|
||||
@@ -1,28 +1,45 @@
|
||||
requests==2.31.0
|
||||
openai==1.61.1
|
||||
colorama==0.4.6
|
||||
python-dotenv==1.0.0
|
||||
playsound==1.3.0
|
||||
soundfile==0.13.1
|
||||
transformers==4.48.3
|
||||
torch==2.5.1
|
||||
ollama==0.4.7
|
||||
scipy==1.15.1
|
||||
kokoro==0.7.12
|
||||
flask==3.1.0
|
||||
soundfile==0.13.1
|
||||
protobuf==3.20.3
|
||||
termcolor==2.5.0
|
||||
ipython==8.34.0
|
||||
gliclass==0.1.8
|
||||
pyaudio==0.2.14
|
||||
librosa==0.10.2.post1
|
||||
selenium==4.29.0
|
||||
markdownify==1.1.0
|
||||
fastapi>=0.115.12
|
||||
flask>=3.1.0
|
||||
celery>=5.5.1
|
||||
aiofiles>=24.1.0
|
||||
uvicorn>=0.34.0
|
||||
pydantic>=2.10.6
|
||||
pydantic_core>=2.27.2
|
||||
setuptools>=75.6.0
|
||||
sacremoses>=0.0.53
|
||||
requests>=2.31.0
|
||||
numpy>=1.24.4
|
||||
colorama>=0.4.6
|
||||
python-dotenv>=1.0.0
|
||||
playsound>=1.3.0
|
||||
soundfile>=0.13.1
|
||||
transformers>=4.46.3
|
||||
torch>=2.4.1
|
||||
python-dotenv>=1.0.0
|
||||
ollama>=0.4.7
|
||||
scipy>=1.9.3
|
||||
kokoro>=0.7.12
|
||||
soundfile>=0.13.1
|
||||
protobuf>=3.20.3
|
||||
termcolor>=2.4.0
|
||||
ipython>=8.13.0
|
||||
pyaudio>=0.2.14
|
||||
librosa>=0.10.2.post1
|
||||
selenium>=4.27.1
|
||||
markdownify>=1.1.0
|
||||
text2emotion>=0.0.5
|
||||
adaptive-classifier>=0.0.10
|
||||
langid>=1.1.6
|
||||
chromedriver-autoinstaller>=0.6.4
|
||||
httpx>=0.27,<0.29
|
||||
anyio>=3.5.0,<5
|
||||
distro>=1.7.0,<2
|
||||
jiter>=0.4.0,<1
|
||||
fake_useragent>=2.1.0
|
||||
selenium_stealth>=1.0.6
|
||||
undetected-chromedriver>=3.5.5
|
||||
sentencepiece>=0.2.0
|
||||
openai
|
||||
sniffio
|
||||
tqdm>4
|
||||
# if use chinese
|
||||
|
||||
@@ -2,16 +2,34 @@
|
||||
|
||||
echo "Starting installation for Linux..."
|
||||
|
||||
set -e
|
||||
# Update package list
|
||||
sudo apt-get update
|
||||
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt
|
||||
sudo apt-get update || { echo "Failed to update package list"; exit 1; }
|
||||
# make sure essential tool are installed
|
||||
# Install essential tools
|
||||
sudo apt-get install -y \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
build-essential \
|
||||
alsa-utils \
|
||||
portaudio19-dev \
|
||||
python3-pyaudio \
|
||||
libgtk-3-dev \
|
||||
libnotify-dev \
|
||||
libgconf-2-4 \
|
||||
libnss3 \
|
||||
libxss1 || { echo "Failed to install packages"; exit 1; }
|
||||
|
||||
# upgrade pip
|
||||
pip install --upgrade pip
|
||||
# install wheel
|
||||
pip install --upgrade pip setuptools wheel
|
||||
# install docker compose
|
||||
sudo apt install -y docker-compose
|
||||
# Install Selenium for chromedriver
|
||||
pip3 install selenium
|
||||
|
||||
# Install portaudio for pyAudio
|
||||
sudo apt-get install -y portaudio19-dev python3-dev alsa-utils
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt --no-cache-dir
|
||||
|
||||
echo "Installation complete for Linux!"
|
||||
@@ -2,16 +2,29 @@
|
||||
|
||||
echo "Starting installation for macOS..."
|
||||
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt
|
||||
set -e
|
||||
|
||||
# Check if homebrew is installed
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Homebrew not found. Installing Homebrew..."
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
fi
|
||||
|
||||
# update
|
||||
brew update
|
||||
# make sure wget installed
|
||||
brew install wget
|
||||
# Install chromedriver using Homebrew
|
||||
brew install --cask chromedriver
|
||||
|
||||
# Install portaudio for pyAudio using Homebrew
|
||||
brew install portaudio
|
||||
|
||||
# update pip
|
||||
python3 -m pip install --upgrade pip
|
||||
# upgrade setuptools and wheel
|
||||
pip3 install --upgrade setuptools wheel
|
||||
# Install Selenium
|
||||
pip3 install selenium
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt --no-cache-dir
|
||||
|
||||
echo "Installation complete for macOS!"
|
||||
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
echo Starting installation for Windows...
|
||||
|
||||
REM Install Python dependencies from requirements.txt
|
||||
pip install pyreadline3
|
||||
pip install -r requirements.txt
|
||||
|
||||
REM Install Selenium
|
||||
pip install selenium
|
||||
|
||||
echo Note: pyAudio installation may require additional steps on Windows.
|
||||
echo Please install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: pip install pyaudio
|
||||
echo Also, download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started
|
||||
echo Place chromedriver in a directory included in your PATH.
|
||||
|
||||
echo Installation partially complete for Windows. Follow manual steps above.
|
||||
pause
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Starting installation for Windows..."
|
||||
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
# Install Selenium
|
||||
pip3 install selenium
|
||||
|
||||
echo "Note: pyAudio installation may require additional steps on Windows."
|
||||
echo "Please install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: pip3 install pyaudio"
|
||||
echo "Also, download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started"
|
||||
echo "Place chromedriver in a directory included in your PATH."
|
||||
|
||||
echo "Installation partially complete for Windows. Follow manual steps above."
|
||||
@@ -1 +0,0 @@
|
||||
SEARXNG_BASE_URL="http://127.0.0.1:8080"
|
||||
@@ -1,3 +1,4 @@
|
||||
version: '3'
|
||||
services:
|
||||
redis:
|
||||
container_name: redis
|
||||
@@ -28,8 +29,8 @@ services:
|
||||
- ./searxng:/etc/searxng:rw
|
||||
environment:
|
||||
- SEARXNG_BASE_URL=http://localhost:8080/
|
||||
- UWSGI_WORKERS=4
|
||||
- UWSGI_THREADS=4
|
||||
- UWSGI_WORKERS=1
|
||||
- UWSGI_THREADS=1
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
|
||||
@@ -95,7 +95,7 @@ server:
|
||||
# If your instance owns a /etc/searxng/settings.yml file, then set the following
|
||||
# values there.
|
||||
|
||||
secret_key: "ultrasecretkey" # Is overwritten by ${SEARXNG_SECRET}
|
||||
secret_key: "supersecret" # Is overwritten by ${SEARXNG_SECRET},W
|
||||
# Proxy image results through SearXNG. Is overwritten by ${SEARXNG_IMAGE_PROXY}
|
||||
image_proxy: false
|
||||
# 1.0 and 1.1 are supported
|
||||
|
||||
@@ -85,17 +85,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop containers
|
||||
echo "Stopping containers to apply security settings..."
|
||||
docker-compose down
|
||||
|
||||
# Start containers again with secure settings
|
||||
echo "Deploying SearXNG with secure settings..."
|
||||
if ! docker-compose up -d; then
|
||||
echo "Error: Failed to deploy SearXNG. Check logs with 'docker compose logs'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Display status and access instructions
|
||||
echo "SearXNG setup complete!"
|
||||
docker ps -a --filter "name=searxng" --filter "name=redis"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
[uwsgi]
|
||||
# Who will run the code
|
||||
uid = searxng
|
||||
gid = searxng
|
||||
|
||||
# Number of workers (usually CPU count)
|
||||
# default value: %k (= number of CPU core, see Dockerfile)
|
||||
workers = 1
|
||||
|
||||
# Number of threads per worker
|
||||
# default value: 4 (see Dockerfile)
|
||||
enable-threads = true
|
||||
threads = 1
|
||||
|
||||
# The right granted on the created socket
|
||||
chmod-socket = 666
|
||||
|
||||
# Plugin to use and interpreter config
|
||||
single-interpreter = true
|
||||
master = true
|
||||
plugin = python3
|
||||
lazy-apps = true
|
||||
enable-threads = 4
|
||||
|
||||
# Module to import
|
||||
module = searx.webapp
|
||||
|
||||
# Virtualenv and python path
|
||||
pythonpath = /usr/local/searxng/
|
||||
chdir = /usr/local/searxng/searx/
|
||||
|
||||
# automatically set processes name to something meaningful
|
||||
auto-procname = true
|
||||
|
||||
# Disable request logging for privacy
|
||||
disable-logging = true
|
||||
log-5xx = true
|
||||
|
||||
# Set the max size of a request (request-body excluded)
|
||||
buffer-size = 8192
|
||||
|
||||
# No keep alive
|
||||
# See https://github.com/searx/searx-docker/issues/24
|
||||
add-header = Connection: close
|
||||
|
||||
# Follow SIGTERM convention
|
||||
# See https://github.com/searxng/searxng/issues/3427
|
||||
die-on-term
|
||||
|
||||
# uwsgi serves the static files
|
||||
static-map = /static=/usr/local/searxng/searx/static
|
||||
static-gzip-all = True
|
||||
offload-threads = 4
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"model_name": "deepseek-r1:14b",
|
||||
"known_models": [
|
||||
"qwq:32b",
|
||||
"deepseek-r1:1.5b",
|
||||
"deepseek-r1:7b",
|
||||
"deepseek-r1:14b",
|
||||
"deepseek-r1:32b",
|
||||
"deepseek-r1:70b",
|
||||
"deepseek-r1:671b",
|
||||
"deepseek-coder:1.3b",
|
||||
"deepseek-coder:6.7b",
|
||||
"deepseek-coder:33b",
|
||||
"llama2-uncensored:7b",
|
||||
"llama2-uncensored:70b",
|
||||
"llama3.1:8b",
|
||||
"llama3.1:70b",
|
||||
"llama3.3:70b",
|
||||
"llama3:8b",
|
||||
"llama3:70b",
|
||||
"i4:14b",
|
||||
"mistral:7b",
|
||||
"mistral:70b",
|
||||
"mistral:33b",
|
||||
"qwen1:7b",
|
||||
"qwen1:14b",
|
||||
"qwen1:32b",
|
||||
"qwen1:70b"
|
||||
]
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
from flask import Flask, jsonify, request
|
||||
import threading
|
||||
import ollama
|
||||
import logging
|
||||
import json
|
||||
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Shared state with thread-safe locks
|
||||
class Config:
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
self.known_models = []
|
||||
self.allowed_models = []
|
||||
self.model_name = None
|
||||
|
||||
def load(self):
|
||||
with open('config.json', 'r') as f:
|
||||
data = json.load(f)
|
||||
self.known_models = data['known_models']
|
||||
self.model_name = data['model_name']
|
||||
|
||||
def validate_model(self, model):
|
||||
if model not in self.known_models:
|
||||
raise ValueError(f"Model {model} is not known")
|
||||
|
||||
class GenerationState:
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
self.last_complete_sentence = ""
|
||||
self.current_buffer = ""
|
||||
self.is_generating = False
|
||||
self.model = None
|
||||
|
||||
state = GenerationState()
|
||||
|
||||
def generate_response(history): # Only takes history as an argument
|
||||
global state
|
||||
try:
|
||||
with state.lock:
|
||||
state.is_generating = True
|
||||
state.last_complete_sentence = ""
|
||||
state.current_buffer = ""
|
||||
|
||||
stream = ollama.chat(
|
||||
model=state.model, # Access state.model directly
|
||||
messages=history,
|
||||
stream=True,
|
||||
)
|
||||
for chunk in stream:
|
||||
content = chunk['message']['content']
|
||||
print(content, end='', flush=True)
|
||||
with state.lock:
|
||||
state.current_buffer += content
|
||||
except ollama.ResponseError as e:
|
||||
if e.status_code == 404:
|
||||
ollama.pull(state.model)
|
||||
with state.lock:
|
||||
state.is_generating = False
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
with state.lock:
|
||||
state.is_generating = False
|
||||
|
||||
@app.route('/generate', methods=['POST'])
|
||||
def start_generation():
|
||||
global state
|
||||
data = request.get_json()
|
||||
|
||||
with state.lock:
|
||||
if state.is_generating:
|
||||
return jsonify({"error": "Generation already in progress"}), 400
|
||||
|
||||
history = data.get('messages', [])
|
||||
# Pass only history to the thread
|
||||
threading.Thread(target=generate_response, args=(history,)).start() # Note the comma to make it a single-element tuple
|
||||
return jsonify({"message": "Generation started"}), 202
|
||||
|
||||
@app.route('/get_updated_sentence')
|
||||
def get_updated_sentence():
|
||||
global state
|
||||
with state.lock:
|
||||
return jsonify({
|
||||
"sentence": state.current_buffer,
|
||||
"is_complete": not state.is_generating
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
config = Config()
|
||||
config.load()
|
||||
config.validate_model(config.model_name)
|
||||
state.model = config.model_name
|
||||
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)
|
||||
@@ -8,36 +8,52 @@ setup(
|
||||
version="0.1.0",
|
||||
author="Fosowl",
|
||||
author_email="mlg.fcu@gmail.com",
|
||||
description="A Python project for agentic search and processing",
|
||||
description="The open, local alternative to ManusAI",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/Fosowl/agenticSeek",
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
"requests==2.31.0",
|
||||
"openai==1.61.1",
|
||||
"colorama==0.4.6",
|
||||
"python-dotenv==1.0.0",
|
||||
"playsound==1.3.0",
|
||||
"soundfile==0.13.1",
|
||||
"transformers==4.48.3",
|
||||
"torch==2.5.1",
|
||||
"ollama==0.4.7",
|
||||
"scipy==1.15.1",
|
||||
"kokoro==0.7.12",
|
||||
"flask==3.1.0",
|
||||
"protobuf==3.20.3",
|
||||
"termcolor==2.5.0",
|
||||
"gliclass==0.1.8",
|
||||
"ipython==8.34.0",
|
||||
"librosa==0.10.2.post1",
|
||||
"selenium==4.29.0",
|
||||
"markdownify==1.1.0",
|
||||
"fastapi>=0.115.12",
|
||||
"celery>=5.5.1",
|
||||
"uvicorn>=0.34.0",
|
||||
"flask>=3.1.0",
|
||||
"aiofiles>=24.1.0",
|
||||
"pydantic>=2.10.6",
|
||||
"pydantic_core>=2.27.2",
|
||||
"requests>=2.31.0",
|
||||
"sacremoses>=0.0.53",
|
||||
"numpy>=1.24.4",
|
||||
"colorama>=0.4.6",
|
||||
"python-dotenv>=1.0.0",
|
||||
"playsound>=1.3.0",
|
||||
"soundfile>=0.13.1",
|
||||
"transformers>=4.46.3",
|
||||
"torch>=2.4.1",
|
||||
"ollama>=0.4.7",
|
||||
"scipy>=1.9.3",
|
||||
"kokoro>=0.7.12",
|
||||
"protobuf>=3.20.3",
|
||||
"termcolor>=2.5.0",
|
||||
"ipython>=8.34.0",
|
||||
"librosa>=0.10.2.post1",
|
||||
"selenium>=4.29.0",
|
||||
"markdownify>=1.1.0",
|
||||
"text2emotion>=0.0.5",
|
||||
"python-dotenv>=1.0.0",
|
||||
"adaptive-classifier>=0.0.10",
|
||||
"langid>=1.1.6",
|
||||
"chromedriver-autoinstaller>=0.6.4",
|
||||
"httpx>=0.27,<0.29",
|
||||
"anyio>=3.5.0,<5",
|
||||
"distro>=1.7.0,<2",
|
||||
"jiter>=0.4.0,<1",
|
||||
"fake_useragent>=2.1.0",
|
||||
"selenium_stealth>=1.0.6",
|
||||
"undetected-chromedriver>=3.5.5",
|
||||
"sentencepiece>=0.2.0",
|
||||
"openai",
|
||||
"sniffio",
|
||||
"tqdm>4"
|
||||
],
|
||||
@@ -59,5 +75,5 @@ setup(
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
python_requires=">=3.6",
|
||||
python_requires=">=3.9",
|
||||
)
|
||||
|
||||
@@ -5,52 +5,86 @@ import os
|
||||
import random
|
||||
import time
|
||||
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from sources.memory import Memory
|
||||
from sources.utility import pretty_print
|
||||
from sources.schemas import executorResult
|
||||
|
||||
random.seed(time.time())
|
||||
|
||||
class executorResult:
|
||||
"""
|
||||
A class to store the result of a tool execution.
|
||||
"""
|
||||
def __init__(self, blocks, feedback, success):
|
||||
self.blocks = blocks
|
||||
self.feedback = feedback
|
||||
self.success = success
|
||||
|
||||
def show(self):
|
||||
for block in self.blocks:
|
||||
pretty_print("-"*100, color="output")
|
||||
pretty_print(block, color="code" if self.success else "failure")
|
||||
pretty_print("-"*100, color="output")
|
||||
pretty_print(self.feedback, color="success" if self.success else "failure")
|
||||
|
||||
class Agent():
|
||||
"""
|
||||
An abstract class for all agents.
|
||||
"""
|
||||
def __init__(self, model: str,
|
||||
name: str,
|
||||
def __init__(self, name: str,
|
||||
prompt_path:str,
|
||||
provider,
|
||||
recover_last_session=True) -> None:
|
||||
verbose=False,
|
||||
browser=None) -> None:
|
||||
"""
|
||||
Args:
|
||||
name (str): Name of the agent.
|
||||
prompt_path (str): Path to the prompt file for the agent.
|
||||
provider: The provider for the LLM.
|
||||
recover_last_session (bool, optional): Whether to recover the last conversation.
|
||||
verbose (bool, optional): Enable verbose logging if True. Defaults to False.
|
||||
browser: The browser class for web navigation (only for browser agent).
|
||||
"""
|
||||
|
||||
self.agent_name = name
|
||||
self.browser = browser
|
||||
self.role = None
|
||||
self.type = None
|
||||
self.current_directory = os.getcwd()
|
||||
self.model = model
|
||||
self.llm = provider
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=recover_last_session,
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False)
|
||||
self.tools = {}
|
||||
self.blocks_result = []
|
||||
self.success = True
|
||||
self.last_answer = ""
|
||||
self.status_message = "Haven't started yet"
|
||||
self.verbose = verbose
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
|
||||
@property
|
||||
def get_agent_name(self) -> str:
|
||||
return self.agent_name
|
||||
|
||||
@property
|
||||
def get_agent_type(self) -> str:
|
||||
return self.type
|
||||
|
||||
@property
|
||||
def get_agent_role(self) -> str:
|
||||
return self.role
|
||||
|
||||
@property
|
||||
def get_last_answer(self) -> str:
|
||||
return self.last_answer
|
||||
|
||||
@property
|
||||
def get_blocks(self) -> list:
|
||||
return self.blocks_result
|
||||
|
||||
@property
|
||||
def get_status_message(self) -> str:
|
||||
return self.status_message
|
||||
|
||||
@property
|
||||
def get_tools(self) -> dict:
|
||||
return self.tools
|
||||
|
||||
@property
|
||||
def get_success(self) -> bool:
|
||||
return self.success
|
||||
|
||||
def get_blocks_result(self) -> list:
|
||||
return self.blocks_result
|
||||
|
||||
def add_tool(self, name: str, tool: Callable) -> None:
|
||||
if tool is not Callable:
|
||||
raise TypeError("Tool must be a callable object (a method)")
|
||||
@@ -85,7 +119,7 @@ class Agent():
|
||||
|
||||
def extract_reasoning_text(self, text: str) -> None:
|
||||
"""
|
||||
Extract the reasoning block of a easoning model like deepseek.
|
||||
Extract the reasoning block of a reasoning model like deepseek.
|
||||
"""
|
||||
start_tag = "<think>"
|
||||
end_tag = "</think>"
|
||||
@@ -93,35 +127,63 @@ class Agent():
|
||||
end_idx = text.rfind(end_tag)+8
|
||||
return text[start_idx:end_idx]
|
||||
|
||||
def llm_request(self, verbose = False) -> Tuple[str, str]:
|
||||
async def llm_request(self) -> Tuple[str, str]:
|
||||
"""
|
||||
Asynchronously ask the LLM to process the prompt.
|
||||
"""
|
||||
self.status_message = "Thinking..."
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(self.executor, self.sync_llm_request)
|
||||
|
||||
def sync_llm_request(self) -> Tuple[str, str]:
|
||||
"""
|
||||
Ask the LLM to process the prompt and return the answer and the reasoning.
|
||||
"""
|
||||
memory = self.memory.get()
|
||||
thought = self.llm.respond(memory, verbose)
|
||||
thought = self.llm.respond(memory, self.verbose)
|
||||
|
||||
reasoning = self.extract_reasoning_text(thought)
|
||||
answer = self.remove_reasoning_text(thought)
|
||||
self.memory.push('assistant', answer)
|
||||
return answer, reasoning
|
||||
|
||||
def wait_message(self, speech_module):
|
||||
async def wait_message(self, speech_module):
|
||||
if speech_module is None:
|
||||
return
|
||||
messages = ["Please be patient, I am working on it.",
|
||||
"Computing... I recommand you have a coffee while I work.",
|
||||
"Hold on, I’m crunching numbers.",
|
||||
"Working on it, please let me think."]
|
||||
speech_module.speak(messages[random.randint(0, len(messages)-1)])
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(self.executor, lambda: speech_module.speak(messages[random.randint(0, len(messages)-1)]))
|
||||
|
||||
def get_blocks_result(self) -> list:
|
||||
return self.blocks_result
|
||||
def get_last_tool_type(self) -> str:
|
||||
return self.blocks_result[-1].tool_type if len(self.blocks_result) > 0 else None
|
||||
|
||||
def raw_answer_blocks(self, answer: str) -> str:
|
||||
"""
|
||||
Return the answer with all the blocks inserted, as text.
|
||||
"""
|
||||
if self.last_answer is None:
|
||||
return
|
||||
raw = ""
|
||||
lines = self.last_answer.split("\n")
|
||||
for line in lines:
|
||||
if "block:" in line:
|
||||
block_idx = int(line.split(":")[1])
|
||||
if block_idx < len(self.blocks_result):
|
||||
raw += self.blocks_result[block_idx].__str__()
|
||||
else:
|
||||
raw += line + "\n"
|
||||
return raw
|
||||
|
||||
def show_answer(self):
|
||||
"""
|
||||
Show the answer in a pretty way.
|
||||
Show code blocks and their respective feedback by inserting them in the ressponse.
|
||||
"""
|
||||
if self.last_answer is None:
|
||||
return
|
||||
lines = self.last_answer.split("\n")
|
||||
for line in lines:
|
||||
if "block:" in line:
|
||||
@@ -130,7 +192,6 @@ class Agent():
|
||||
self.blocks_result[block_idx].show()
|
||||
else:
|
||||
pretty_print(line, color="output")
|
||||
self.blocks_result = []
|
||||
|
||||
def remove_blocks(self, text: str) -> str:
|
||||
"""
|
||||
@@ -153,28 +214,41 @@ class Agent():
|
||||
block_idx += 1
|
||||
return "\n".join(post_lines)
|
||||
|
||||
def show_block(self, block: str) -> None:
|
||||
"""
|
||||
Show the block in a pretty way.
|
||||
"""
|
||||
pretty_print('▂'*64, color="status")
|
||||
pretty_print(block, color="code")
|
||||
pretty_print('▂'*64, color="status")
|
||||
|
||||
def execute_modules(self, answer: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Execute all the tools the agent has and return the result.
|
||||
"""
|
||||
feedback = ""
|
||||
success = False
|
||||
success = True
|
||||
blocks = None
|
||||
if answer.startswith("```"):
|
||||
answer = "I will execute:\n" + answer # there should always be a text before blocks for the function that display answer
|
||||
|
||||
self.success = True
|
||||
for name, tool in self.tools.items():
|
||||
feedback = ""
|
||||
blocks, save_path = tool.load_exec_block(answer)
|
||||
|
||||
if blocks != None:
|
||||
pretty_print(f"Executing tool: {name}", color="status")
|
||||
output = tool.execute(blocks)
|
||||
for block in blocks:
|
||||
self.show_block(block)
|
||||
output = tool.execute([block])
|
||||
feedback = tool.interpreter_feedback(output) # tool interpreter feedback
|
||||
success = not tool.execution_failure_check(output)
|
||||
pretty_print(feedback, color="success" if success else "failure")
|
||||
self.memory.push('user', feedback)
|
||||
self.blocks_result.append(executorResult(blocks, feedback, success))
|
||||
self.blocks_result.append(executorResult(block, feedback, success, name))
|
||||
if not success:
|
||||
self.success = False
|
||||
self.memory.push('user', feedback)
|
||||
return False, feedback
|
||||
self.memory.push('user', feedback)
|
||||
if save_path != None:
|
||||
tool.save_block(blocks, save_path)
|
||||
return True, feedback
|
||||
|
||||
@@ -1,101 +1,195 @@
|
||||
import re
|
||||
import time
|
||||
from datetime import date
|
||||
from typing import List, Tuple, Type, Dict
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
from sources.tools.searxSearch import searxSearch
|
||||
from sources.browser import Browser
|
||||
from sources.logger import Logger
|
||||
|
||||
class Action(Enum):
|
||||
REQUEST_EXIT = "REQUEST_EXIT"
|
||||
FORM_FILLED = "FORM_FILLED"
|
||||
GO_BACK = "GO_BACK"
|
||||
NAVIGATE = "NAVIGATE"
|
||||
SEARCH = "SEARCH"
|
||||
|
||||
class BrowserAgent(Agent):
|
||||
def __init__(self, model, name, prompt_path, provider):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
|
||||
"""
|
||||
The Browser agent is an agent that navigate the web autonomously in search of answer
|
||||
"""
|
||||
super().__init__(model, name, prompt_path, provider)
|
||||
super().__init__(name, prompt_path, provider, verbose, browser)
|
||||
self.tools = {
|
||||
"web_search": searxSearch(),
|
||||
}
|
||||
self.role = "deep research and web search"
|
||||
self.browser = Browser()
|
||||
self.browser.go_to("https://github.com/")
|
||||
self.role = "web"
|
||||
self.type = "browser_agent"
|
||||
self.browser = browser
|
||||
self.current_page = ""
|
||||
self.search_history = []
|
||||
self.navigable_links = []
|
||||
self.last_action = Action.NAVIGATE.value
|
||||
self.notes = []
|
||||
self.date = self.get_today_date()
|
||||
self.logger = Logger("browser_agent.log")
|
||||
|
||||
def get_today_date(self) -> str:
|
||||
"""Get the date"""
|
||||
date_time = date.today()
|
||||
return date_time.strftime("%B %d, %Y")
|
||||
|
||||
def extract_links(self, search_result: str):
|
||||
def extract_links(self, search_result: str) -> List[str]:
|
||||
"""Extract all links from a sentence."""
|
||||
pattern = r'(https?://\S+|www\.\S+)'
|
||||
matches = re.findall(pattern, search_result)
|
||||
trailing_punct = ".,!?;:"
|
||||
trailing_punct = ".,!?;:)"
|
||||
cleaned_links = [link.rstrip(trailing_punct) for link in matches]
|
||||
self.logger.info(f"Extracted links: {cleaned_links}")
|
||||
return self.clean_links(cleaned_links)
|
||||
|
||||
def clean_links(self, links: list):
|
||||
def extract_form(self, text: str) -> List[str]:
|
||||
"""Extract form written by the LLM in format [input_name](value)"""
|
||||
inputs = []
|
||||
matches = re.findall(r"\[\w+\]\([^)]+\)", text)
|
||||
return matches
|
||||
|
||||
def clean_links(self, links: List[str]) -> List[str]:
|
||||
"""Ensure no '.' at the end of link"""
|
||||
links_clean = []
|
||||
for link in links:
|
||||
link = link.strip()
|
||||
if link[-1] == '.':
|
||||
if not (link[-1].isalpha() or link[-1].isdigit()):
|
||||
links_clean.append(link[:-1])
|
||||
else:
|
||||
links_clean.append(link)
|
||||
return links_clean
|
||||
|
||||
def get_unvisited_links(self):
|
||||
def get_unvisited_links(self) -> List[str]:
|
||||
return "\n".join([f"[{i}] {link}" for i, link in enumerate(self.navigable_links) if link not in self.search_history])
|
||||
|
||||
def make_newsearch_prompt(self, user_prompt: str, search_result: dict):
|
||||
def make_newsearch_prompt(self, user_prompt: str, search_result: dict) -> str:
|
||||
search_choice = self.stringify_search_results(search_result)
|
||||
self.logger.info(f"Search results: {search_choice}")
|
||||
return f"""
|
||||
Based on the search result:
|
||||
{search_choice}
|
||||
Your goal is to find accurate and complete information to satisfy the user’s request.
|
||||
User request: {user_prompt}
|
||||
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I want to navigate to <link>."
|
||||
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I will navigate to <link>"
|
||||
Do not explain your choice.
|
||||
"""
|
||||
|
||||
def make_navigation_prompt(self, user_prompt: str, page_text: str):
|
||||
def make_navigation_prompt(self, user_prompt: str, page_text: str) -> str:
|
||||
remaining_links = self.get_unvisited_links()
|
||||
remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, proceed with a new search."
|
||||
return f"""
|
||||
\nYou are currently browsing the web. Not the user, you are the browser.
|
||||
remaining_links_text = remaining_links if remaining_links is not None else "No links remaining, do a new search."
|
||||
inputs_form = self.browser.get_form_inputs()
|
||||
inputs_form_text = '\n'.join(inputs_form)
|
||||
notes = '\n'.join(self.notes)
|
||||
self.logger.info(f"Making navigation prompt with page text: {page_text[:100]}...\nremaining links: {remaining_links_text}")
|
||||
self.logger.info(f"Inputs form: {inputs_form_text}")
|
||||
self.logger.info(f"Notes: {notes}")
|
||||
|
||||
Page content:
|
||||
return f"""
|
||||
You are navigating the web.
|
||||
|
||||
**Current Context**
|
||||
|
||||
Webpage ({self.current_page}) content:
|
||||
{page_text}
|
||||
|
||||
You can navigate to these links:
|
||||
{remaining_links}
|
||||
Allowed Navigation Links:
|
||||
{remaining_links_text}
|
||||
|
||||
If no link seem appropriate, please say "GO_BACK".
|
||||
Remember, you seek the information the user want.
|
||||
The user query was : {user_prompt}
|
||||
You must choose a link (write it down) to navigate to, or go back.
|
||||
For exemple you can say: i want to go to www.wikipedia.org/cats
|
||||
Always end with a sentence that summarize when useful information is found for exemple:
|
||||
Summary: According to https://karpathy.github.io/ LeCun net is the earliest real-world application of a neural net"
|
||||
Do not say "according to this page", always write down the whole link.
|
||||
If a website does not have usefull information say Error, for exemple:
|
||||
Error: This forum does not discus anything that can answer the user query
|
||||
Do not explain your choice, be short, concise.
|
||||
Inputs forms:
|
||||
{inputs_form_text}
|
||||
|
||||
End of webpage ({self.current_page}.
|
||||
|
||||
# Instruction
|
||||
|
||||
1. **Evaluate if the page is relevant for user’s query and document finding:**
|
||||
- If the page is relevant, extract and summarize key information in concise notes (Note: <your note>)
|
||||
- If page not relevant, state: "Error: <specific reason the page does not address the query>" and either return to the previous page or navigate to a new link.
|
||||
- Notes should be factual, useful summaries of relevant content, they should always include specific names or link. Written as: "On <website URL>, <key fact 1>. <Key fact 2>. <Additional insight>." Avoid phrases like "the page provides" or "I found that."
|
||||
2. **Navigate to a link by either: **
|
||||
- Saying I will navigate to (write down the full URL) www.example.com/cats
|
||||
- Going back: If no link seems helpful, say: {Action.GO_BACK.value}.
|
||||
3. **Fill forms on the page:**
|
||||
- Fill form only when relevant.
|
||||
- Use Login if username/password specified by user. For quick task create account, remember password in a note.
|
||||
- You can fill a form using [form_name](value). Don't {Action.GO_BACK.value} when filling form.
|
||||
- If a form is irrelevant or you lack informations (eg: don't know user email) leave it empty.
|
||||
4. **Decide if you completed the task**
|
||||
- Check your notes. Do they fully answer the question? Did you verify with multiple pages?
|
||||
- Are you sure it’s correct?
|
||||
- If yes to all, say {Action.REQUEST_EXIT}.
|
||||
- If no, or a page lacks info, go to another link.
|
||||
- Never stop or ask the user for help.
|
||||
|
||||
**Rules:**
|
||||
- Do not write "The page talk about ...", write your finding on the page and how they contribute to an answer.
|
||||
- Put note in a single paragraph.
|
||||
- When you exit, explain why.
|
||||
|
||||
# Example:
|
||||
|
||||
Example 1 (useful page, no need go futher):
|
||||
Note: According to karpathy site LeCun net is ...
|
||||
No link seem useful to provide futher information.
|
||||
Action: {Action.GO_BACK.value}
|
||||
|
||||
Example 2 (not useful, see useful link on page):
|
||||
Error: reddit.com/welcome does not discuss anything related to the user’s query.
|
||||
There is a link that could lead to the information.
|
||||
Action: navigate to http://reddit.com/r/locallama
|
||||
|
||||
Example 3 (not useful, no related links):
|
||||
Error: x.com does not discuss anything related to the user’s query and no navigation link are usefull.
|
||||
Action: {Action.GO_BACK.value}
|
||||
|
||||
Example 3 (clear definitive query answer found or enought notes taken):
|
||||
I took 10 notes so far with enought finding to answer user question.
|
||||
Therefore I should exit the web browser.
|
||||
Action: {Action.REQUEST_EXIT.value}
|
||||
|
||||
Example 4 (loging form visible):
|
||||
|
||||
Note: I am on the login page, I will type the given username and password.
|
||||
Action:
|
||||
[username_field](David)
|
||||
[password_field](edgerunners77)
|
||||
|
||||
Remember, user asked:
|
||||
{user_prompt}
|
||||
You previously took these notes:
|
||||
{notes}
|
||||
Do not Step-by-Step explanation. Write comprehensive Notes or Error as a long paragraph followed by your action.
|
||||
You must always take notes.
|
||||
"""
|
||||
|
||||
def llm_decide(self, prompt):
|
||||
async def llm_decide(self, prompt: str, show_reasoning: bool = False) -> Tuple[str, str]:
|
||||
animate_thinking("Thinking...", color="status")
|
||||
self.memory.push('user', prompt)
|
||||
answer, reasoning = self.llm_request(prompt)
|
||||
pretty_print("-"*100)
|
||||
answer, reasoning = await self.llm_request()
|
||||
if show_reasoning:
|
||||
pretty_print(reasoning, color="failure")
|
||||
pretty_print(answer, color="output")
|
||||
pretty_print("-"*100)
|
||||
return answer, reasoning
|
||||
|
||||
def select_unvisited(self, search_result):
|
||||
def select_unvisited(self, search_result: List[str]) -> List[str]:
|
||||
results_unvisited = []
|
||||
for res in search_result:
|
||||
if res["link"] not in self.search_history:
|
||||
results_unvisited.append(res)
|
||||
self.logger.info(f"Unvisited links: {results_unvisited}")
|
||||
return results_unvisited
|
||||
|
||||
def jsonify_search_results(self, results_string):
|
||||
def jsonify_search_results(self, results_string: str) -> List[str]:
|
||||
result_blocks = results_string.split("\n\n")
|
||||
parsed_results = []
|
||||
for block in result_blocks:
|
||||
@@ -114,62 +208,206 @@ class BrowserAgent(Agent):
|
||||
parsed_results.append(result_dict)
|
||||
return parsed_results
|
||||
|
||||
def stringify_search_results(self, results_arr):
|
||||
return '\n\n'.join([f"Link: {res['link']}" for res in results_arr])
|
||||
def stringify_search_results(self, results_arr: List[str]) -> str:
|
||||
return '\n\n'.join([f"Link: {res['link']}\nPreview: {res['snippet']}" for res in results_arr])
|
||||
|
||||
def save_notes(self, text):
|
||||
def parse_answer(self, text):
|
||||
lines = text.split('\n')
|
||||
saving = False
|
||||
buffer = []
|
||||
links = []
|
||||
for line in lines:
|
||||
if "summary:" in line.lower():
|
||||
self.notes.append(line)
|
||||
if line == '' or 'action:' in line.lower():
|
||||
saving = False
|
||||
if "note" in line.lower():
|
||||
saving = True
|
||||
if saving:
|
||||
buffer.append(line.replace("notes:", ''))
|
||||
else:
|
||||
links.extend(self.extract_links(line))
|
||||
self.notes.append('. '.join(buffer).strip())
|
||||
return links
|
||||
|
||||
def conclude_prompt(self, user_query):
|
||||
search_note = '\n -'.join(self.notes)
|
||||
def select_link(self, links: List[str]) -> str | None:
|
||||
for lk in links:
|
||||
if lk == self.current_page:
|
||||
self.logger.info(f"Already visited {lk}. Skipping.")
|
||||
continue
|
||||
self.logger.info(f"Selected link: {lk}")
|
||||
return lk
|
||||
self.logger.warning("No link selected.")
|
||||
return None
|
||||
|
||||
def conclude_prompt(self, user_query: str) -> str:
|
||||
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
|
||||
search_note = '\n'.join(annotated_notes)
|
||||
pretty_print(f"AI notes:\n{search_note}", color="success")
|
||||
return f"""
|
||||
Following a web search about:
|
||||
Following a human request:
|
||||
{user_query}
|
||||
Write a conclusion based on these notes:
|
||||
A web browsing AI made the following finding across different pages:
|
||||
{search_note}
|
||||
|
||||
Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible.
|
||||
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
|
||||
"""
|
||||
|
||||
def process(self, user_prompt, speech_module) -> str:
|
||||
def search_prompt(self, user_prompt: str) -> str:
|
||||
return f"""
|
||||
Current date: {self.date}
|
||||
Make a efficient search engine query to help users with their request:
|
||||
{user_prompt}
|
||||
Example:
|
||||
User: "go to twitter, login with username toto and password pass79 to my twitter and say hello everyone "
|
||||
You: search: Twitter login page.
|
||||
|
||||
User: "I need info on the best laptops for AI this year."
|
||||
You: "search: best laptops 2025 to run Machine Learning model, reviews"
|
||||
|
||||
User: "Search for recent news about space missions."
|
||||
You: "search: Recent space missions news, {self.date}"
|
||||
|
||||
Do not explain, do not write anything beside the search query.
|
||||
Except if query does not make any sense for a web search then explain why and say {Action.REQUEST_EXIT.value}
|
||||
Do not try to answer query. you can only formulate search term or exit.
|
||||
"""
|
||||
|
||||
def handle_update_prompt(self, user_prompt: str, page_text: str, fill_success: bool) -> str:
|
||||
prompt = f"""
|
||||
You are a web browser.
|
||||
You just filled a form on the page.
|
||||
Now you should see the result of the form submission on the page:
|
||||
Page text:
|
||||
{page_text}
|
||||
The user asked: {user_prompt}
|
||||
Does the page answer the user’s query now? Are you still on a login page or did you get redirected?
|
||||
If it does, take notes of the useful information, write down result and say {Action.FORM_FILLED.value}.
|
||||
if it doesn’t, say: Error: Attempt to fill form didn't work {Action.GO_BACK.value}.
|
||||
If you were previously on a login form, no need to take notes.
|
||||
"""
|
||||
if not fill_success:
|
||||
prompt += f"""
|
||||
According to browser feedback, the form was not filled correctly. Is that so? you might consider other strategies.
|
||||
"""
|
||||
return prompt
|
||||
|
||||
def show_search_results(self, search_result: List[str]):
|
||||
pretty_print("\nSearch results:", color="output")
|
||||
for res in search_result:
|
||||
pretty_print(f"Title: {res['title']} - ", color="info", no_newline=True)
|
||||
pretty_print(f"Link: {res['link']}", color="status")
|
||||
|
||||
def stuck_prompt(self, user_prompt: str, unvisited: List[str]) -> str:
|
||||
"""
|
||||
Prompt for when the agent repeat itself, can happen when fail to extract a link.
|
||||
"""
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
prompt += f"""
|
||||
You previously said:
|
||||
{self.last_answer}
|
||||
You must consider other options. Choose other link.
|
||||
"""
|
||||
return prompt
|
||||
|
||||
async def process(self, user_prompt: str, speech_module: type) -> Tuple[str, str]:
|
||||
"""
|
||||
Process the user prompt to conduct an autonomous web search.
|
||||
Start with a google search with searxng using web_search tool.
|
||||
Then enter a navigation logic to find the answer or conduct required actions.
|
||||
Args:
|
||||
user_prompt: The user's input query
|
||||
speech_module: Optional speech output module
|
||||
Returns:
|
||||
tuple containing the final answer and reasoning
|
||||
"""
|
||||
complete = False
|
||||
|
||||
animate_thinking(f"Thinking...", color="status")
|
||||
mem_begin_idx = self.memory.push('user', self.search_prompt(user_prompt))
|
||||
ai_prompt, reasoning = await self.llm_request()
|
||||
if Action.REQUEST_EXIT.value in ai_prompt:
|
||||
pretty_print(f"Web agent requested exit.\n{reasoning}\n\n{ai_prompt}", color="failure")
|
||||
return ai_prompt, ""
|
||||
animate_thinking(f"Searching...", color="status")
|
||||
search_result_raw = self.tools["web_search"].execute([user_prompt], False)
|
||||
search_result = self.jsonify_search_results(search_result_raw)
|
||||
search_result = search_result[:10] # until futher improvement
|
||||
self.status_message = "Searching..."
|
||||
search_result_raw = self.tools["web_search"].execute([ai_prompt], False)
|
||||
search_result = self.jsonify_search_results(search_result_raw)[:16]
|
||||
self.show_search_results(search_result)
|
||||
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
||||
unvisited = [None]
|
||||
while not complete:
|
||||
answer, reasoning = self.llm_decide(prompt)
|
||||
self.save_notes(answer)
|
||||
if "REQUEST_EXIT" in answer:
|
||||
complete = True
|
||||
break
|
||||
links = self.extract_links(answer)
|
||||
if len(links) == 0 or "GO_BACK" in answer:
|
||||
while not complete and len(unvisited) > 0:
|
||||
|
||||
self.memory.clear()
|
||||
unvisited = self.select_unvisited(search_result)
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
pretty_print(f"Going back to results. Still {len(unvisited)}", color="warning")
|
||||
links = []
|
||||
answer, reasoning = await self.llm_decide(prompt, show_reasoning = False)
|
||||
if self.last_answer == answer:
|
||||
prompt = self.stuck_prompt(user_prompt, unvisited)
|
||||
continue
|
||||
if len(unvisited) == 0:
|
||||
break
|
||||
animate_thinking(f"Navigating to {links[0]}", color="status")
|
||||
speech_module.speak(f"Navigating to {links[0]}")
|
||||
self.browser.go_to(links[0])
|
||||
self.search_history.append(links[0])
|
||||
self.last_answer = answer
|
||||
pretty_print('▂'*32, color="status")
|
||||
|
||||
extracted_form = self.extract_form(answer)
|
||||
if len(extracted_form) > 0:
|
||||
self.status_message = "Filling web form..."
|
||||
pretty_print(f"Filling inputs form...", color="status")
|
||||
fill_success = self.browser.fill_form(extracted_form)
|
||||
page_text = self.browser.get_text()
|
||||
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
|
||||
answer, reasoning = await self.llm_decide(prompt)
|
||||
|
||||
if Action.FORM_FILLED.value in answer:
|
||||
pretty_print(f"Filled form. Handling page update.", color="status")
|
||||
page_text = self.browser.get_text()
|
||||
self.navigable_links = self.browser.get_navigable()
|
||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||
continue
|
||||
|
||||
speech_module.speak(answer)
|
||||
self.browser.close()
|
||||
links = self.parse_answer(answer)
|
||||
link = self.select_link(links)
|
||||
if link == self.current_page:
|
||||
pretty_print(f"Already visited {link}. Search callback.", color="status")
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
self.search_history.append(link)
|
||||
continue
|
||||
|
||||
if Action.REQUEST_EXIT.value in answer:
|
||||
self.status_message = "Exiting web browser..."
|
||||
pretty_print(f"Agent requested exit.", color="status")
|
||||
complete = True
|
||||
break
|
||||
|
||||
if (link == None and len(extracted_form) < 3) or Action.GO_BACK.value in answer or link in self.search_history:
|
||||
pretty_print(f"Going back to results. Still {len(unvisited)}", color="status")
|
||||
self.status_message = "Going back to search results..."
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
self.search_history.append(link)
|
||||
self.current_page = link
|
||||
continue
|
||||
|
||||
animate_thinking(f"Navigating to {link}", color="status")
|
||||
if speech_module: speech_module.speak(f"Navigating to {link}")
|
||||
nav_ok = self.browser.go_to(link)
|
||||
self.search_history.append(link)
|
||||
if not nav_ok:
|
||||
pretty_print(f"Failed to navigate to {link}.", color="failure")
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
continue
|
||||
self.current_page = link
|
||||
page_text = self.browser.get_text()
|
||||
self.navigable_links = self.browser.get_navigable()
|
||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||
self.status_message = "Navigating..."
|
||||
self.browser.screenshot()
|
||||
|
||||
pretty_print("Exited navigation, starting to summarize finding...", color="status")
|
||||
prompt = self.conclude_prompt(user_prompt)
|
||||
answer, reasoning = self.llm_request(prompt)
|
||||
mem_last_idx = self.memory.push('user', prompt)
|
||||
self.status_message = "Summarizing findings..."
|
||||
answer, reasoning = await self.llm_request()
|
||||
pretty_print(answer, color="output")
|
||||
self.status_message = "Ready"
|
||||
self.last_answer = answer
|
||||
return answer, reasoning
|
||||
|
||||
if __name__ == "__main__":
|
||||
browser = Browser()
|
||||
pass
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
@@ -7,41 +8,23 @@ from sources.tools.fileFinder import FileFinder
|
||||
from sources.tools.BashInterpreter import BashInterpreter
|
||||
|
||||
class CasualAgent(Agent):
|
||||
def __init__(self, model, name, prompt_path, provider):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
"""
|
||||
The casual agent is a special for casual talk to the user without specific tasks.
|
||||
"""
|
||||
super().__init__(model, name, prompt_path, provider)
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
self.tools = {
|
||||
"web_search": searxSearch(),
|
||||
"flight_search": FlightSearch(),
|
||||
"file_finder": FileFinder(),
|
||||
"bash": BashInterpreter()
|
||||
}
|
||||
self.role = "casual talking"
|
||||
} # No tools for the casual agent
|
||||
self.role = "talk"
|
||||
self.type = "casual_agent"
|
||||
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
complete = False
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
self.memory.push('user', prompt)
|
||||
|
||||
self.wait_message(speech_module)
|
||||
while not complete:
|
||||
animate_thinking("Thinking...", color="status")
|
||||
answer, reasoning = self.llm_request()
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
answer = self.remove_blocks(answer)
|
||||
answer, reasoning = await self.llm_request()
|
||||
self.last_answer = answer
|
||||
complete = True
|
||||
for tool in self.tools.values():
|
||||
if tool.found_executable_blocks():
|
||||
complete = False # AI read results and continue the conversation
|
||||
self.status_message = "Ready"
|
||||
return answer, reasoning
|
||||
|
||||
if __name__ == "__main__":
|
||||
from llm_provider import Provider
|
||||
|
||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
||||
agent = CasualAgent("deepseek-r1:14b", "jarvis", "prompts/casual_agent.txt", server_provider)
|
||||
ans = agent.process("Hello, how are you?")
|
||||
print(ans)
|
||||
pass
|
||||
@@ -1,3 +1,5 @@
|
||||
import platform, os
|
||||
import asyncio
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent, executorResult
|
||||
@@ -5,50 +7,77 @@ from sources.tools.C_Interpreter import CInterpreter
|
||||
from sources.tools.GoInterpreter import GoInterpreter
|
||||
from sources.tools.PyInterpreter import PyInterpreter
|
||||
from sources.tools.BashInterpreter import BashInterpreter
|
||||
from sources.tools.JavaInterpreter import JavaInterpreter
|
||||
from sources.tools.fileFinder import FileFinder
|
||||
from sources.logger import Logger
|
||||
|
||||
class CoderAgent(Agent):
|
||||
"""
|
||||
The code agent is an agent that can write and execute code.
|
||||
"""
|
||||
def __init__(self, model, name, prompt_path, provider):
|
||||
super().__init__(model, name, prompt_path, provider)
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
self.tools = {
|
||||
"bash": BashInterpreter(),
|
||||
"python": PyInterpreter(),
|
||||
"c": CInterpreter(),
|
||||
"go": GoInterpreter(),
|
||||
"java": JavaInterpreter(),
|
||||
"file_finder": FileFinder()
|
||||
}
|
||||
self.role = "coding and programming"
|
||||
self.work_dir = self.tools["file_finder"].get_work_dir()
|
||||
self.role = "code"
|
||||
self.type = "code_agent"
|
||||
self.logger = Logger("code_agent.log")
|
||||
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
def add_sys_info_prompt(self, prompt):
|
||||
"""Add system information to the prompt."""
|
||||
info = f"System Info:\n" \
|
||||
f"OS: {platform.system()} {platform.release()}\n" \
|
||||
f"Python Version: {platform.python_version()}\n" \
|
||||
f"\nYou must save file at root directory: {self.work_dir}"
|
||||
return f"{prompt}\n\n{info}"
|
||||
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
answer = ""
|
||||
attempt = 0
|
||||
max_attempts = 3
|
||||
max_attempts = 4
|
||||
prompt = self.add_sys_info_prompt(prompt)
|
||||
self.memory.push('user', prompt)
|
||||
clarify_trigger = "REQUEST_CLARIFICATION"
|
||||
|
||||
while attempt < max_attempts:
|
||||
animate_thinking("Thinking...", color="status")
|
||||
self.wait_message(speech_module)
|
||||
answer, reasoning = self.llm_request()
|
||||
animate_thinking("Executing code...", color="status")
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
answer = self.remove_blocks(answer)
|
||||
await self.wait_message(speech_module)
|
||||
answer, reasoning = await self.llm_request()
|
||||
if clarify_trigger in answer:
|
||||
self.last_answer = answer
|
||||
if exec_success:
|
||||
await asyncio.sleep(0)
|
||||
return answer, reasoning
|
||||
if not "```" in answer:
|
||||
self.last_answer = answer
|
||||
await asyncio.sleep(0)
|
||||
break
|
||||
self.show_answer()
|
||||
animate_thinking("Executing code...", color="status")
|
||||
self.status_message = "Executing code..."
|
||||
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
self.logger.info(f"Execution result: {exec_success}")
|
||||
answer = self.remove_blocks(answer)
|
||||
self.last_answer = answer
|
||||
await asyncio.sleep(0)
|
||||
if exec_success and self.get_last_tool_type() != "bash":
|
||||
break
|
||||
pretty_print("Execution failure", color="failure")
|
||||
pretty_print("Correcting code...", color="status")
|
||||
self.status_message = "Correcting code..."
|
||||
attempt += 1
|
||||
self.status_message = "Ready"
|
||||
if attempt == max_attempts:
|
||||
return "I'm sorry, I couldn't find a solution to your problem. How would you like me to proceed ?", reasoning
|
||||
self.last_answer = answer
|
||||
return answer, reasoning
|
||||
|
||||
if __name__ == "__main__":
|
||||
from llm_provider import Provider
|
||||
|
||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
||||
agent = CoderAgent("deepseek-r1:14b", "jarvis", "prompts/coder_agent.txt", server_provider)
|
||||
ans = agent.process("What is the output of 5+5 in python ?")
|
||||
print(ans)
|
||||
pass
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
@@ -5,42 +6,32 @@ from sources.tools.fileFinder import FileFinder
|
||||
from sources.tools.BashInterpreter import BashInterpreter
|
||||
|
||||
class FileAgent(Agent):
|
||||
def __init__(self, model, name, prompt_path, provider):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
"""
|
||||
The file agent is a special agent for file operations.
|
||||
"""
|
||||
super().__init__(model, name, prompt_path, provider)
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
self.tools = {
|
||||
"file_finder": FileFinder(),
|
||||
"bash": BashInterpreter()
|
||||
}
|
||||
self.role = "files operations"
|
||||
self.work_dir = self.tools["file_finder"].get_work_dir()
|
||||
self.role = "files"
|
||||
self.type = "file_agent"
|
||||
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
complete = False
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
exec_success = False
|
||||
prompt += f"\nYou must work in directory: {self.work_dir}"
|
||||
self.memory.push('user', prompt)
|
||||
|
||||
self.wait_message(speech_module)
|
||||
while not complete:
|
||||
if exec_success:
|
||||
complete = True
|
||||
while exec_success is False:
|
||||
await self.wait_message(speech_module)
|
||||
animate_thinking("Thinking...", color="status")
|
||||
answer, reasoning = self.llm_request()
|
||||
answer, reasoning = await self.llm_request()
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
answer = self.remove_blocks(answer)
|
||||
self.last_answer = answer
|
||||
complete = True
|
||||
for name, tool in self.tools.items():
|
||||
if tool.found_executable_blocks():
|
||||
complete = False # AI read results and continue the conversation
|
||||
self.status_message = "Ready"
|
||||
return answer, reasoning
|
||||
|
||||
if __name__ == "__main__":
|
||||
from llm_provider import Provider
|
||||
|
||||
#local_provider = Provider("ollama", "deepseek-r1:14b", None)
|
||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
||||
agent = FileAgent("deepseek-r1:14b", "jarvis", "prompts/file_agent.txt", server_provider)
|
||||
ans = agent.process("What is the content of the file toto.py ?")
|
||||
print(ans)
|
||||
pass
|
||||
@@ -1,98 +1,274 @@
|
||||
import json
|
||||
from typing import List, Tuple, Type, Dict
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
from sources.agents.code_agent import CoderAgent
|
||||
from sources.agents.file_agent import FileAgent
|
||||
from sources.agents.browser_agent import BrowserAgent
|
||||
from sources.agents.casual_agent import CasualAgent
|
||||
from sources.text_to_speech import Speech
|
||||
from sources.tools.tools import Tools
|
||||
from sources.logger import Logger
|
||||
|
||||
class PlannerAgent(Agent):
|
||||
def __init__(self, model, name, prompt_path, provider):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
|
||||
"""
|
||||
The planner agent is a special agent that divides and conquers the task.
|
||||
"""
|
||||
super().__init__(model, name, prompt_path, provider)
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
self.tools = {
|
||||
"json": Tools()
|
||||
}
|
||||
self.tools['json'].tag = "json"
|
||||
self.browser = browser
|
||||
self.agents = {
|
||||
"coder": CoderAgent(model, name, prompt_path, provider),
|
||||
"file": FileAgent(model, name, prompt_path, provider),
|
||||
"web": CasualAgent(model, name, prompt_path, provider)
|
||||
"coder": CoderAgent(name, "prompts/base/coder_agent.txt", provider, verbose=False),
|
||||
"file": FileAgent(name, "prompts/base/file_agent.txt", provider, verbose=False),
|
||||
"web": BrowserAgent(name, "prompts/base/browser_agent.txt", provider, verbose=False, browser=browser),
|
||||
"casual": CasualAgent(name, "prompts/base/casual_agent.txt", provider, verbose=False)
|
||||
}
|
||||
self.role = "complex programming tasks and web research"
|
||||
self.tag = "json"
|
||||
self.role = "planification"
|
||||
self.type = "planner_agent"
|
||||
self.logger = Logger("planner_agent.log")
|
||||
|
||||
def parse_agent_tasks(self, text):
|
||||
tasks = []
|
||||
def get_task_names(self, text: str) -> List[str]:
|
||||
"""
|
||||
Extracts task names from the given text.
|
||||
This method processes a multi-line string, where each line may represent a task name.
|
||||
containing '##' or starting with a digit. The valid task names are collected and returned.
|
||||
Args:
|
||||
text (str): A string containing potential task titles (eg: Task 1: I will...).
|
||||
Returns:
|
||||
List[str]: A list of extracted task names that meet the specified criteria.
|
||||
"""
|
||||
tasks_names = []
|
||||
|
||||
lines = text.strip().split('\n')
|
||||
for line in lines:
|
||||
if line is None or len(line) == 0:
|
||||
if line is None:
|
||||
continue
|
||||
line = line.strip()
|
||||
if len(line) == 0:
|
||||
continue
|
||||
if '##' in line or line[0].isdigit():
|
||||
tasks_names.append(line)
|
||||
continue
|
||||
self.logger.info(f"Found {len(tasks_names)} tasks names.")
|
||||
return tasks_names
|
||||
|
||||
def parse_agent_tasks(self, text: str) -> List[Tuple[str, str]]:
|
||||
"""
|
||||
Parses agent tasks from the given LLM text.
|
||||
This method extracts task information from a JSON. It identifies task names and their details.
|
||||
Args:
|
||||
text (str): The input text containing task information in a JSON-like format.
|
||||
Returns:
|
||||
List[Tuple[str, str]]: A list of tuples containing task names and their details.
|
||||
"""
|
||||
tasks = []
|
||||
tasks_names = self.get_task_names(text)
|
||||
|
||||
blocks, _ = self.tools["json"].load_exec_block(text)
|
||||
if blocks == None:
|
||||
return (None, None)
|
||||
return []
|
||||
for block in blocks:
|
||||
line_json = json.loads(block)
|
||||
if 'plan' in line_json:
|
||||
for task in line_json['plan']:
|
||||
if task['agent'].lower() not in [ag_name.lower() for ag_name in self.agents.keys()]:
|
||||
self.logger.warning(f"Agent {task['agent']} does not exist.")
|
||||
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
|
||||
return []
|
||||
agent = {
|
||||
'agent': task['agent'],
|
||||
'id': task['id'],
|
||||
'task': task['task']
|
||||
}
|
||||
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
|
||||
if 'need' in task:
|
||||
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
|
||||
agent['need'] = task['need']
|
||||
tasks.append(agent)
|
||||
if len(tasks_names) != len(tasks):
|
||||
names = [task['task'] for task in tasks]
|
||||
return zip(names, tasks)
|
||||
return zip(tasks_names, tasks)
|
||||
return list(map(list, zip(names, tasks)))
|
||||
return list(map(list, zip(tasks_names, tasks)))
|
||||
|
||||
def make_prompt(self, task, needed_infos):
|
||||
def make_prompt(self, task: str, agent_infos_dict: dict) -> str:
|
||||
"""
|
||||
Generates a prompt for the agent based on the task and previous agents work information.
|
||||
Args:
|
||||
task (str): The task to be performed.
|
||||
agent_infos_dict (dict): A dictionary containing information from other agents.
|
||||
Returns:
|
||||
str: The formatted prompt for the agent.
|
||||
"""
|
||||
infos = ""
|
||||
if agent_infos_dict is None or len(agent_infos_dict) == 0:
|
||||
infos = "No needed informations."
|
||||
else:
|
||||
for agent_id, info in agent_infos_dict.items():
|
||||
infos += f"\t- According to agent {agent_id}:\n{info}\n\n"
|
||||
prompt = f"""
|
||||
You are given the following informations:
|
||||
{needed_infos}
|
||||
You are given informations from your AI friends work:
|
||||
{infos}
|
||||
Your task is:
|
||||
{task}
|
||||
"""
|
||||
self.logger.info(f"Prompt for agent:\n{prompt}")
|
||||
return prompt
|
||||
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
self.memory.push('user', prompt)
|
||||
self.wait_message(speech_module)
|
||||
animate_thinking("Thinking...", color="status")
|
||||
agents_tasks = (None, None)
|
||||
answer, reasoning = self.llm_request()
|
||||
agents_tasks = self.parse_agent_tasks(answer)
|
||||
if agents_tasks == (None, None):
|
||||
return "Failed to parse the tasks", reasoning
|
||||
def show_plan(self, agents_tasks: List[dict], answer: str) -> None:
|
||||
"""
|
||||
Displays the plan made by the agent.
|
||||
Args:
|
||||
agents_tasks (dict): The tasks assigned to each agent.
|
||||
answer (str): The answer from the LLM.
|
||||
"""
|
||||
if agents_tasks == []:
|
||||
pretty_print(answer, color="warning")
|
||||
pretty_print("Failed to make a plan. This can happen with (too) small LLM. Clarify your request and insist on it making a plan within ```json.", color="failure")
|
||||
return
|
||||
pretty_print("\n▂▘ P L A N ▝▂", color="status")
|
||||
for task_name, task in agents_tasks:
|
||||
pretty_print(f"I will {task_name}.", color="info")
|
||||
agent_prompt = self.make_prompt(task['task'], task['need'])
|
||||
pretty_print(f"Assigned agent {task['agent']} to {task_name}", color="info")
|
||||
speech_module.speak(f"I will {task_name}. I assigned the {task['agent']} agent to the task.")
|
||||
try:
|
||||
self.agents[task['agent'].lower()].process(agent_prompt, None)
|
||||
pretty_print(f"-- Agent answer ---\n\n", color="output")
|
||||
self.agents[task['agent'].lower()].show_answer()
|
||||
pretty_print(f"\n\n", color="output")
|
||||
except Exception as e:
|
||||
pretty_print(f"Error: {e}", color="failure")
|
||||
speech_module.speak(f"I encountered an error: {e}")
|
||||
break
|
||||
self.last_answer = answer
|
||||
return answer, reasoning
|
||||
pretty_print(f"{task['agent']} -> {task['task']}", color="info")
|
||||
pretty_print("▔▗ E N D ▖▔", color="status")
|
||||
|
||||
if __name__ == "__main__":
|
||||
from llm_provider import Provider
|
||||
server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000")
|
||||
agent = PlannerAgent("deepseek-r1:14b", "jarvis", "prompts/planner_agent.txt", server_provider)
|
||||
ans = agent.process("Make a cool game to illustrate the current relation between USA and europe")
|
||||
async def make_plan(self, prompt: str) -> str:
|
||||
"""
|
||||
Asks the LLM to make a plan.
|
||||
Args:
|
||||
prompt (str): The prompt to be sent to the LLM.
|
||||
Returns:
|
||||
str: The plan made by the LLM.
|
||||
"""
|
||||
ok = False
|
||||
answer = None
|
||||
while not ok:
|
||||
animate_thinking("Thinking...", color="status")
|
||||
self.memory.push('user', prompt)
|
||||
answer, reasoning = await self.llm_request()
|
||||
if "NO_UPDATE" in answer:
|
||||
return []
|
||||
agents_tasks = self.parse_agent_tasks(answer)
|
||||
if agents_tasks == []:
|
||||
prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n"
|
||||
pretty_print("Failed to make plan. Retrying...", color="warning")
|
||||
continue
|
||||
self.show_plan(agents_tasks, answer)
|
||||
ok = True
|
||||
self.logger.info(f"Plan made:\n{answer}")
|
||||
return self.parse_agent_tasks(answer)
|
||||
|
||||
async def update_plan(self, goal: str, agents_tasks: List[dict], agents_work_result: dict, id: str, success: bool) -> dict:
|
||||
"""
|
||||
Updates the plan with the results of the agents work.
|
||||
Args:
|
||||
goal (str): The goal to be achieved.
|
||||
agents_tasks (list): The tasks assigned to each agent.
|
||||
agents_work_result (dict): The results of the agents work.
|
||||
Returns:
|
||||
dict: The updated plan.
|
||||
"""
|
||||
self.status_message = "Updating plan..."
|
||||
last_agent_work = agents_work_result[id]
|
||||
tool_success_str = "success" if success else "failure"
|
||||
pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure")
|
||||
if int(id) == len(agents_tasks):
|
||||
next_task = "No task follow, this was the last step. If it failed add a task to recover."
|
||||
else:
|
||||
next_task = f"Next task is: {agents_tasks[int(id)][0]}."
|
||||
#if success:
|
||||
# return agents_tasks # we only update the plan if last task failed, for now
|
||||
update_prompt = f"""
|
||||
Your goal is : {goal}
|
||||
You previously made a plan, agents are currently working on it.
|
||||
The last agent working on task: {id}, did the following work:
|
||||
{last_agent_work}
|
||||
Agent {id} work was a {tool_success_str} according to system interpreter.
|
||||
{next_task}
|
||||
Is the work done for task {id} leading to sucess or failure ? Did an agent fail with a task?
|
||||
If agent work was good: answer "NO_UPDATE"
|
||||
If agent work is leading to failure: update the plan.
|
||||
If a task failed add a task to try again or recover from failure. You might have near identical task twice.
|
||||
plan should be within ```json like before.
|
||||
You need to rewrite the whole plan, but only change the tasks after task {id}.
|
||||
Make the plan the same length as the original one or with only one additional step.
|
||||
Do not change past tasks. Change next tasks.
|
||||
"""
|
||||
pretty_print("Updating plan...", color="status")
|
||||
plan = await self.make_plan(update_prompt)
|
||||
if plan == []:
|
||||
pretty_print("No plan update required.", color="info")
|
||||
return agents_tasks
|
||||
self.logger.info(f"Plan updated:\n{plan}")
|
||||
return plan
|
||||
|
||||
async def start_agent_process(self, task: dict, required_infos: dict | None) -> str:
|
||||
"""
|
||||
Starts the agent process for a given task.
|
||||
Args:
|
||||
task (dict): The task to be performed.
|
||||
required_infos (dict | None): The required information for the task.
|
||||
Returns:
|
||||
str: The result of the agent process.
|
||||
"""
|
||||
self.status_message = f"Starting task {task['task']}..."
|
||||
agent_prompt = self.make_prompt(task['task'], required_infos)
|
||||
pretty_print(f"Agent {task['agent']} started working...", color="status")
|
||||
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
|
||||
answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None)
|
||||
self.last_answer = answer
|
||||
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
|
||||
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
|
||||
success = self.agents[task['agent'].lower()].get_success
|
||||
self.agents[task['agent'].lower()].show_answer()
|
||||
pretty_print(f"Agent {task['agent']} completed task.", color="status")
|
||||
self.logger.info(f"Agent {task['agent']} finished working on {task['task']}. Success: {success}")
|
||||
agent_answer += "\nAgent succeeded with task." if success else "\nAgent failed with task (Error detected)."
|
||||
return agent_answer, success
|
||||
|
||||
def get_work_result_agent(self, task_needs, agents_work_result):
|
||||
res = {k: agents_work_result[k] for k in task_needs if k in agents_work_result}
|
||||
self.logger.info(f"Next agent needs: {task_needs}.\n Match previous agent result: {res}")
|
||||
return res
|
||||
|
||||
async def process(self, goal: str, speech_module: Speech) -> Tuple[str, str]:
|
||||
"""
|
||||
Process the goal by dividing it into tasks and assigning them to agents.
|
||||
Args:
|
||||
goal (str): The goal to be achieved (user prompt).
|
||||
speech_module (Speech): The speech module for text-to-speech.
|
||||
Returns:
|
||||
Tuple[str, str]: The result of the agent process and empty reasoning string.
|
||||
"""
|
||||
agents_tasks = []
|
||||
required_infos = None
|
||||
agents_work_result = dict()
|
||||
|
||||
self.status_message = "Making a plan..."
|
||||
agents_tasks = await self.make_plan(goal)
|
||||
|
||||
if agents_tasks == []:
|
||||
return "Failed to parse the tasks.", ""
|
||||
i = 0
|
||||
steps = len(agents_tasks)
|
||||
while i < steps:
|
||||
task_name, task = agents_tasks[i][0], agents_tasks[i][1]
|
||||
self.status_message = "Starting agents..."
|
||||
pretty_print(f"I will {task_name}.", color="info")
|
||||
self.last_answer = f"I will {task_name.lower()}."
|
||||
pretty_print(f"Assigned agent {task['agent']} to {task_name}", color="info")
|
||||
if speech_module: speech_module.speak(f"I will {task_name}. I assigned the {task['agent']} agent to the task.")
|
||||
|
||||
if agents_work_result is not None:
|
||||
required_infos = self.get_work_result_agent(task['need'], agents_work_result)
|
||||
try:
|
||||
answer, success = await self.start_agent_process(task, required_infos)
|
||||
except Exception as e:
|
||||
raise e
|
||||
agents_work_result[task['id']] = answer
|
||||
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
|
||||
steps = len(agents_tasks)
|
||||
i += 1
|
||||
|
||||
return answer, ""
|
||||
@@ -5,82 +5,234 @@ from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import TimeoutException, WebDriverException
|
||||
import time
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from typing import List, Tuple, Type, Dict
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import urlparse
|
||||
from fake_useragent import UserAgent
|
||||
from selenium_stealth import stealth
|
||||
import undetected_chromedriver as uc
|
||||
import chromedriver_autoinstaller
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import markdownify
|
||||
import logging
|
||||
import sys
|
||||
import re
|
||||
|
||||
class Browser:
|
||||
def __init__(self, headless=False, anticaptcha_install=False):
|
||||
"""Initialize the browser with optional headless mode."""
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Referer': 'https://www.google.com/',
|
||||
}
|
||||
self.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
|
||||
try:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.logger import Logger
|
||||
|
||||
def get_chrome_path() -> str:
|
||||
"""Get the path to the Chrome executable."""
|
||||
if sys.platform.startswith("win"):
|
||||
paths = [
|
||||
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
||||
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Google\\Chrome\\Application\\chrome.exe") # User install
|
||||
]
|
||||
elif sys.platform.startswith("darwin"): # macOS
|
||||
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
|
||||
else: # Linux
|
||||
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/opt/chrome/chrome", "/usr/local/bin/chrome"]
|
||||
|
||||
for path in paths:
|
||||
if os.path.exists(path) and os.access(path, os.X_OK): # Check if executable
|
||||
return path
|
||||
print("Looking for Google Chrome in these locations failed:")
|
||||
print('\n'.join(paths))
|
||||
chrome_path_env = os.environ.get("CHROME_EXECUTABLE_PATH")
|
||||
if chrome_path_env and os.path.exists(chrome_path_env) and os.access(chrome_path_env, os.X_OK):
|
||||
return chrome_path_env
|
||||
path = input("Google Chrome not found. Please enter the path to the Chrome executable: ")
|
||||
if os.path.exists(path) and os.access(path, os.X_OK):
|
||||
os.environ["CHROME_EXECUTABLE_PATH"] = path
|
||||
print(f"Chrome path saved to environment variable CHROME_EXECUTABLE_PATH")
|
||||
return path
|
||||
return None
|
||||
|
||||
def get_random_user_agent() -> str:
|
||||
"""Get a random user agent string with associated vendor."""
|
||||
user_agents = [
|
||||
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "vendor": "Google Inc."},
|
||||
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "vendor": "Apple Inc."},
|
||||
{"ua": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "vendor": ""},
|
||||
]
|
||||
return random.choice(user_agents)
|
||||
|
||||
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome:
|
||||
"""Create a Chrome WebDriver with specified options."""
|
||||
chrome_options = Options()
|
||||
chrome_path = get_chrome_path()
|
||||
|
||||
if not chrome_path:
|
||||
raise FileNotFoundError("Google Chrome not found. Please install it.")
|
||||
chrome_options.binary_location = chrome_path
|
||||
|
||||
if headless:
|
||||
chrome_options.add_argument("--headless")
|
||||
chrome_options.add_argument("--disable-gpu")
|
||||
chrome_options.add_argument("--disable-webgl")
|
||||
user_data_dir = tempfile.mkdtemp()
|
||||
user_agent = get_random_user_agent()
|
||||
chrome_options.add_argument(f"--user-data-dir={user_data_dir}")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
self.driver = webdriver.Chrome(options=chrome_options)
|
||||
chrome_options.add_argument("--mute-audio")
|
||||
chrome_options.add_argument("--disable-notifications")
|
||||
chrome_options.add_argument("--autoplay-policy=user-gesture-required")
|
||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
chrome_options.add_argument(f'user-agent={user_agent["ua"]}')
|
||||
resolutions = [(1920, 1080), (1366, 768), (1440, 900)]
|
||||
width, height = random.choice(resolutions)
|
||||
chrome_options.add_argument(f'--window-size={width},{height}')
|
||||
if not stealth_mode:
|
||||
# crx file can't be installed in stealth mode
|
||||
if not os.path.exists(crx_path):
|
||||
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
|
||||
else:
|
||||
chrome_options.add_extension(crx_path)
|
||||
|
||||
chromedriver_path = shutil.which("chromedriver")
|
||||
if not chromedriver_path:
|
||||
chromedriver_path = chromedriver_autoinstaller.install()
|
||||
|
||||
if not chromedriver_path:
|
||||
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
|
||||
|
||||
service = Service(chromedriver_path)
|
||||
if stealth_mode:
|
||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
driver = uc.Chrome(service=service, options=chrome_options)
|
||||
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
||||
chrome_version = driver.capabilities['browserVersion']
|
||||
stealth(driver,
|
||||
languages=["en-US", "en"],
|
||||
vendor=user_agent["vendor"],
|
||||
platform="Win64" if "Windows" in user_agent["ua"] else "MacIntel" if "Macintosh" in user_agent["ua"] else "Linux x86_64",
|
||||
webgl_vendor="Intel Inc.",
|
||||
renderer="Intel Iris OpenGL Engine",
|
||||
fix_hairline=True,
|
||||
)
|
||||
return driver
|
||||
security_prefs = {
|
||||
"profile.default_content_setting_values.media_stream": 2,
|
||||
"profile.default_content_setting_values.geolocation": 2,
|
||||
"safebrowsing.enabled": True,
|
||||
}
|
||||
chrome_options.add_experimental_option("prefs", security_prefs)
|
||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
chrome_options.add_experimental_option('useAutomationExtension', False)
|
||||
return webdriver.Chrome(service=service, options=chrome_options)
|
||||
|
||||
class Browser:
|
||||
def __init__(self, driver, anticaptcha_manual_install=False):
|
||||
"""Initialize the browser with optional AntiCaptcha installation."""
|
||||
self.js_scripts_folder = "./sources/web_scripts/" if not __name__ == "__main__" else "./web_scripts/"
|
||||
self.anticaptcha = "https://chrome.google.com/webstore/detail/nopecha-captcha-solver/dknlfmjaanfblgfdfebhijalfmhmjjjo/related"
|
||||
self.logger = Logger("browser.log")
|
||||
self.screenshot_folder = os.path.join(os.getcwd(), ".screenshots")
|
||||
self.tabs = []
|
||||
try:
|
||||
self.driver = driver
|
||||
self.wait = WebDriverWait(self.driver, 10)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info("Browser initialized successfully")
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to initialize browser: {str(e)}")
|
||||
self.setup_tabs()
|
||||
if anticaptcha_manual_install:
|
||||
self.load_anticatpcha_manually()
|
||||
|
||||
def go_to(self, url):
|
||||
def setup_tabs(self):
|
||||
self.tabs = self.driver.window_handles
|
||||
self.driver.get("https://www.google.com")
|
||||
self.screenshot()
|
||||
|
||||
def switch_control_tab(self):
|
||||
self.logger.log("Switching to control tab.")
|
||||
self.driver.switch_to.window(self.tabs[0])
|
||||
|
||||
def load_anticatpcha_manually(self):
|
||||
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
|
||||
self.driver.get(self.anticaptcha)
|
||||
|
||||
def go_to(self, url:str) -> bool:
|
||||
"""Navigate to a specified URL."""
|
||||
time.sleep(random.uniform(0.4, 2.5)) # more human behavior
|
||||
try:
|
||||
initial_handles = self.driver.window_handles
|
||||
self.driver.get(url)
|
||||
time.sleep(2) # Wait for page to load
|
||||
self.logger.info(f"Navigated to: {url}")
|
||||
try:
|
||||
wait = WebDriverWait(self.driver, timeout=10)
|
||||
wait.until(
|
||||
lambda driver: (
|
||||
not any(keyword in driver.page_source.lower() for keyword in ["checking your browser", "captcha"])
|
||||
),
|
||||
message="stuck on 'checking browser' or verification screen"
|
||||
)
|
||||
except TimeoutException:
|
||||
self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'")
|
||||
self.apply_web_safety()
|
||||
self.logger.log(f"Navigated to: {url}")
|
||||
return True
|
||||
except TimeoutException as e:
|
||||
self.logger.error(f"Timeout waiting for {url} to load: {str(e)}")
|
||||
return False
|
||||
except WebDriverException as e:
|
||||
self.logger.error(f"Error navigating to {url}: {str(e)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fatal error with go_to method on {url}:\n{str(e)}")
|
||||
raise e
|
||||
|
||||
def is_sentence(self, text):
|
||||
def is_sentence(self, text:str) -> bool:
|
||||
"""Check if the text qualifies as a meaningful sentence or contains important error codes."""
|
||||
text = text.strip()
|
||||
error_codes = ["404", "403", "500", "502", "503"]
|
||||
if any(code in text for code in error_codes):
|
||||
return True
|
||||
words = text.split()
|
||||
word_count = len(words)
|
||||
has_punctuation = text.endswith(('.', '!', '?'))
|
||||
is_long_enough = word_count > 5
|
||||
has_letters = any(word.isalpha() for word in words)
|
||||
return (word_count >= 5 and (has_punctuation or is_long_enough) and has_letters)
|
||||
|
||||
def get_text(self):
|
||||
"""Get page text and convert it to README (Markdown) format."""
|
||||
if any(c.isdigit() for c in text):
|
||||
return True
|
||||
words = re.findall(r'\w+', text, re.UNICODE)
|
||||
word_count = len(words)
|
||||
has_punctuation = any(text.endswith(p) for p in ['.', ',', ',', '!', '?', '。', '!', '?', '।', '۔'])
|
||||
is_long_enough = word_count > 4
|
||||
return (word_count >= 5 and (has_punctuation or is_long_enough))
|
||||
|
||||
def get_text(self) -> str | None:
|
||||
"""Get page text as formatted Markdown"""
|
||||
try:
|
||||
soup = BeautifulSoup(self.driver.page_source, 'html.parser')
|
||||
|
||||
for element in soup(['script', 'style']):
|
||||
for element in soup(['script', 'style', 'noscript', 'meta', 'link']):
|
||||
element.decompose()
|
||||
|
||||
text = soup.get_text()
|
||||
|
||||
lines = (line.strip() for line in text.splitlines())
|
||||
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
||||
text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk))
|
||||
|
||||
markdown_text = markdownify.markdownify(text, heading_style="ATX")
|
||||
|
||||
return markdown_text
|
||||
markdown_converter = markdownify.MarkdownConverter(
|
||||
heading_style="ATX",
|
||||
strip=['a'],
|
||||
autolinks=False,
|
||||
bullets='•',
|
||||
strong_em_symbol='*',
|
||||
default_title=False,
|
||||
)
|
||||
markdown_text = markdown_converter.convert(str(soup.body))
|
||||
lines = []
|
||||
for line in markdown_text.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped and self.is_sentence(stripped):
|
||||
cleaned = ' '.join(stripped.split())
|
||||
lines.append(cleaned)
|
||||
result = "[Start of page]\n\n" + "\n\n".join(lines) + "\n\n[End of page]"
|
||||
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
|
||||
self.logger.info(f"Extracted text: {result[:100]}...")
|
||||
self.logger.info(f"Extracted text length: {len(result)}")
|
||||
return result[:8192]
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting text: {str(e)}")
|
||||
return None
|
||||
|
||||
def clean_url(self, url):
|
||||
def clean_url(self, url:str) -> str:
|
||||
"""Clean URL to keep only the part needed for navigation to the page"""
|
||||
clean = url.split('#')[0]
|
||||
parts = clean.split('?', 1)
|
||||
base_url = parts[0]
|
||||
@@ -96,7 +248,25 @@ class Browser:
|
||||
return f"{base_url}?{'&'.join(essential_params)}"
|
||||
return base_url
|
||||
|
||||
def get_navigable(self):
|
||||
def is_link_valid(self, url:str) -> bool:
|
||||
"""Check if a URL is a valid link (page, not related to icon or metadata)."""
|
||||
if len(url) > 72:
|
||||
self.logger.warning(f"URL too long: {url}")
|
||||
return False
|
||||
parsed_url = urlparse(url)
|
||||
if not parsed_url.scheme or not parsed_url.netloc:
|
||||
self.logger.warning(f"Invalid URL: {url}")
|
||||
return False
|
||||
if re.search(r'/\d+$', parsed_url.path):
|
||||
return False
|
||||
image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']
|
||||
metadata_extensions = ['.ico', '.xml', '.json', '.rss', '.atom']
|
||||
for ext in image_extensions + metadata_extensions:
|
||||
if url.lower().endswith(ext):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_navigable(self) -> List[str]:
|
||||
"""Get all navigable links on the current page."""
|
||||
try:
|
||||
links = []
|
||||
@@ -112,77 +282,334 @@ class Browser:
|
||||
})
|
||||
|
||||
self.logger.info(f"Found {len(links)} navigable links")
|
||||
return [self.clean_url(link['url']) for link in links if link['is_displayed'] == True and len(link) < 256]
|
||||
return [self.clean_url(link['url']) for link in links if (link['is_displayed'] == True and self.is_link_valid(link['url']))]
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting navigable links: {str(e)}")
|
||||
return []
|
||||
|
||||
def click_element(self, xpath):
|
||||
"""Click an element specified by xpath."""
|
||||
def click_element(self, xpath: str) -> bool:
|
||||
"""Click an element specified by XPath."""
|
||||
try:
|
||||
element = self.wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, xpath))
|
||||
)
|
||||
element = self.wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))
|
||||
if not element.is_displayed():
|
||||
return False
|
||||
if not element.is_enabled():
|
||||
return False
|
||||
try:
|
||||
self.logger.error(f"Scrolling to element for click_element.")
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
|
||||
time.sleep(0.1)
|
||||
element.click()
|
||||
time.sleep(2) # Wait for action to complete
|
||||
self.logger.info(f"Clicked element at {xpath}")
|
||||
return True
|
||||
except ElementClickInterceptedException as e:
|
||||
self.logger.error(f"Error click_element: {str(e)}")
|
||||
return False
|
||||
except TimeoutException:
|
||||
self.logger.error(f"Element not found or not clickable: {xpath}")
|
||||
self.logger.warning(f"Timeout clicking element.")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected error clicking element at {xpath}: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_current_url(self):
|
||||
def load_js(self, file_name: str) -> str:
|
||||
"""Load javascript from script folder to inject to page."""
|
||||
path = os.path.join(self.js_scripts_folder, file_name)
|
||||
self.logger.info(f"Loading js at {path}")
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError as e:
|
||||
raise Exception(f"Could not find: {path}") from e
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def find_all_inputs(self, timeout=3):
|
||||
"""Find all inputs elements on the page."""
|
||||
try:
|
||||
WebDriverWait(self.driver, timeout).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error waiting for input element: {str(e)}")
|
||||
return []
|
||||
time.sleep(0.5)
|
||||
script = self.load_js("find_inputs.js")
|
||||
input_elements = self.driver.execute_script(script)
|
||||
return input_elements
|
||||
|
||||
def get_form_inputs(self) -> List[str]:
|
||||
"""Extract all input from the page and return them."""
|
||||
try:
|
||||
input_elements = self.find_all_inputs()
|
||||
if not input_elements:
|
||||
self.logger.info("No input element on page.")
|
||||
return ["No input forms found on the page."]
|
||||
|
||||
form_strings = []
|
||||
for element in input_elements:
|
||||
input_type = element.get("type") or "text"
|
||||
if input_type in ["hidden", "submit", "button", "image"] or not element["displayed"]:
|
||||
continue
|
||||
input_name = element.get("text") or element.get("id") or input_type
|
||||
if input_type == "checkbox" or input_type == "radio":
|
||||
try:
|
||||
checked_status = "checked" if element.is_selected() else "unchecked"
|
||||
except Exception as e:
|
||||
continue
|
||||
form_strings.append(f"[{input_name}]({checked_status})")
|
||||
else:
|
||||
form_strings.append(f"[{input_name}]("")")
|
||||
return form_strings
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def get_buttons_xpath(self) -> List[str]:
|
||||
"""
|
||||
Find buttons and return their type and xpath.
|
||||
"""
|
||||
buttons = self.driver.find_elements(By.TAG_NAME, "button") + \
|
||||
self.driver.find_elements(By.XPATH, "//input[@type='submit']")
|
||||
result = []
|
||||
for i, button in enumerate(buttons):
|
||||
if not button.is_displayed() or not button.is_enabled():
|
||||
continue
|
||||
text = (button.text or button.get_attribute("value") or "").lower().replace(' ', '')
|
||||
xpath = f"(//button | //input[@type='submit'])[{i + 1}]"
|
||||
result.append((text, xpath))
|
||||
result.sort(key=lambda x: len(x[0]))
|
||||
return result
|
||||
|
||||
def wait_for_submission_outcome(self, timeout: int = 10) -> bool:
|
||||
"""
|
||||
Wait for a submission outcome (e.g., URL change or new element).
|
||||
"""
|
||||
try:
|
||||
self.logger.info("Waiting for submission outcome...")
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
wait.until(
|
||||
lambda driver: driver.current_url != self.driver.current_url or
|
||||
driver.find_elements(By.XPATH, "//*[contains(text(), 'success')]")
|
||||
)
|
||||
self.logger.info("Detected submission outcome")
|
||||
return True
|
||||
except TimeoutException:
|
||||
self.logger.warning("No submission outcome detected")
|
||||
return False
|
||||
|
||||
def find_and_click_btn(self, btn_type: str = 'login', timeout: int = 5) -> bool:
|
||||
"""Find and click a submit button matching the specified type."""
|
||||
buttons = self.get_buttons_xpath()
|
||||
if not buttons:
|
||||
self.logger.warning("No visible buttons found")
|
||||
return False
|
||||
|
||||
for button_text, xpath in buttons:
|
||||
if btn_type.lower() in button_text.lower() or btn_type.lower() in xpath.lower():
|
||||
try:
|
||||
wait = WebDriverWait(self.driver, timeout)
|
||||
element = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, xpath)),
|
||||
message=f"Button with XPath '{xpath}' not clickable within {timeout} seconds"
|
||||
)
|
||||
if self.click_element(xpath):
|
||||
self.logger.info(f"Clicked button '{button_text}' at XPath: {xpath}")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(f"Button '{button_text}' at XPath: {xpath} not clickable")
|
||||
return False
|
||||
except TimeoutException:
|
||||
self.logger.warning(f"Timeout waiting for '{button_text}' button at XPath: {xpath}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error clicking button '{button_text}' at XPath: {xpath} - {str(e)}")
|
||||
return False
|
||||
self.logger.warning(f"No button matching '{btn_type}' found")
|
||||
return False
|
||||
|
||||
def tick_all_checkboxes(self) -> bool:
|
||||
"""
|
||||
Find and tick all checkboxes on the page.
|
||||
Returns True if successful, False if any issues occur.
|
||||
"""
|
||||
try:
|
||||
checkboxes = self.driver.find_elements(By.XPATH, "//input[@type='checkbox']")
|
||||
if not checkboxes:
|
||||
self.logger.info("No checkboxes found on the page")
|
||||
return True
|
||||
|
||||
for index, checkbox in enumerate(checkboxes, 1):
|
||||
try:
|
||||
WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable(checkbox)
|
||||
)
|
||||
self.driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block: 'center', inline: 'center'});", checkbox
|
||||
)
|
||||
if not checkbox.is_selected():
|
||||
try:
|
||||
checkbox.click()
|
||||
self.logger.info(f"Ticked checkbox {index}")
|
||||
except ElementClickInterceptedException:
|
||||
self.driver.execute_script("arguments[0].click();", checkbox)
|
||||
self.logger.warning(f"Click checkbox {index} intercepted")
|
||||
else:
|
||||
self.logger.info(f"Checkbox {index} already ticked")
|
||||
except TimeoutException:
|
||||
self.logger.warning(f"Timeout waiting for checkbox {index} to be clickable")
|
||||
continue
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error ticking checkbox {index}: {str(e)}")
|
||||
continue
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error finding checkboxes: {str(e)}")
|
||||
return False
|
||||
|
||||
def find_and_click_submission(self, timeout: int = 10) -> bool:
|
||||
possible_submissions = ["login", "submit", "register", "continue", "apply",
|
||||
"ok", "confirm", "proceed", "accept",
|
||||
"done", "finish", "start", "calculate"]
|
||||
for submission in possible_submissions:
|
||||
if self.find_and_click_btn(submission, timeout):
|
||||
self.logger.info(f"Clicked on submission button: {submission}")
|
||||
return True
|
||||
self.logger.warning("No submission button found")
|
||||
return False
|
||||
|
||||
def find_input_xpath_by_name(self, inputs, name: str) -> str | None:
|
||||
for field in inputs:
|
||||
if name in field["text"]:
|
||||
return field["xpath"]
|
||||
return None
|
||||
|
||||
def fill_form_inputs(self, input_list: List[str]) -> bool:
|
||||
"""Fill inputs based on a list of [name](value) strings."""
|
||||
if not isinstance(input_list, list):
|
||||
self.logger.error("input_list must be a list")
|
||||
return False
|
||||
inputs = self.find_all_inputs()
|
||||
try:
|
||||
for input_str in input_list:
|
||||
match = re.match(r'\[(.*?)\]\((.*?)\)', input_str)
|
||||
if not match:
|
||||
self.logger.warning(f"Invalid format for input: {input_str}")
|
||||
continue
|
||||
|
||||
name, value = match.groups()
|
||||
name = name.strip()
|
||||
value = value.strip()
|
||||
xpath = self.find_input_xpath_by_name(inputs, name)
|
||||
if not xpath:
|
||||
self.logger.warning(f"Input field '{name}' not found")
|
||||
continue
|
||||
try:
|
||||
element = WebDriverWait(self.driver, 10).until(
|
||||
EC.element_to_be_clickable((By.XPATH, xpath))
|
||||
)
|
||||
except TimeoutException:
|
||||
self.logger.error(f"Timeout waiting for element '{name}' to be clickable")
|
||||
continue
|
||||
self.driver.execute_script("arguments[0].scrollIntoView(true);", element)
|
||||
if not element.is_displayed() or not element.is_enabled():
|
||||
self.logger.warning(f"Element '{name}' is not interactable (not displayed or disabled)")
|
||||
continue
|
||||
input_type = (element.get_attribute("type") or "text").lower()
|
||||
if input_type in ["checkbox", "radio"]:
|
||||
is_checked = element.is_selected()
|
||||
should_be_checked = value.lower() == "checked"
|
||||
|
||||
if is_checked != should_be_checked:
|
||||
element.click()
|
||||
self.logger.info(f"Set {name} to {value}")
|
||||
else:
|
||||
element.clear()
|
||||
element.send_keys(value)
|
||||
self.logger.info(f"Filled {name} with {value}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error filling form inputs: {str(e)}")
|
||||
return False
|
||||
|
||||
def fill_form(self, input_list: List[str]) -> bool:
|
||||
"""Fill form inputs based on a list of [name](value) and submit."""
|
||||
if not isinstance(input_list, list):
|
||||
self.logger.error("input_list must be a list")
|
||||
return False
|
||||
if self.fill_form_inputs(input_list):
|
||||
self.logger.info("Form filled successfully")
|
||||
self.tick_all_checkboxes()
|
||||
if self.find_and_click_submission():
|
||||
if self.wait_for_submission_outcome():
|
||||
self.logger.info("Submission outcome detected")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning("No submission outcome detected")
|
||||
else:
|
||||
self.logger.warning("Failed to submit form")
|
||||
self.logger.warning("Failed to fill form inputs")
|
||||
return False
|
||||
|
||||
def get_current_url(self) -> str:
|
||||
"""Get the current URL of the page."""
|
||||
return self.driver.current_url
|
||||
|
||||
def get_page_title(self):
|
||||
def get_page_title(self) -> str:
|
||||
"""Get the title of the current page."""
|
||||
return self.driver.title
|
||||
|
||||
def scroll_bottom(self):
|
||||
def scroll_bottom(self) -> bool:
|
||||
"""Scroll to the bottom of the page."""
|
||||
try:
|
||||
self.logger.info("Scrolling to the bottom of the page...")
|
||||
self.driver.execute_script(
|
||||
"window.scrollTo(0, document.body.scrollHeight);"
|
||||
)
|
||||
time.sleep(1) # Wait for scroll to complete
|
||||
time.sleep(0.5)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error scrolling: {str(e)}")
|
||||
return False
|
||||
|
||||
def screenshot(self, filename):
|
||||
def get_screenshot(self) -> str:
|
||||
return self.screenshot_folder + "/updated_screen.png"
|
||||
|
||||
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
|
||||
"""Take a screenshot of the current page."""
|
||||
self.logger.info("Taking screenshot...")
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
self.driver.save_screenshot(filename)
|
||||
path = os.path.join(self.screenshot_folder, filename)
|
||||
if not os.path.exists(self.screenshot_folder):
|
||||
os.makedirs(self.screenshot_folder)
|
||||
self.driver.save_screenshot(path)
|
||||
self.logger.info(f"Screenshot saved as {filename}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error taking screenshot: {str(e)}")
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
"""Close the browser."""
|
||||
try:
|
||||
self.driver.quit()
|
||||
self.logger.info("Browser closed")
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor to ensure browser is closed."""
|
||||
self.close()
|
||||
def apply_web_safety(self):
|
||||
"""
|
||||
Apply security measures to block any website malicious/annoying execution, privacy violation etc..
|
||||
"""
|
||||
self.logger.info("Applying web safety measures...")
|
||||
script = self.load_js("inject_safety_script.js")
|
||||
input_elements = self.driver.execute_script(script)
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
driver = create_driver(headless=False, stealth_mode=True)
|
||||
browser = Browser(driver, anticaptcha_manual_install=True)
|
||||
|
||||
browser = Browser(headless=False)
|
||||
|
||||
try:
|
||||
browser.go_to("https://karpathy.github.io/")
|
||||
text = browser.get_text()
|
||||
print("Page Text in Markdown:")
|
||||
print(text)
|
||||
links = browser.get_navigable()
|
||||
print("\nNavigable Links:", links)
|
||||
finally:
|
||||
browser.close()
|
||||
input("press enter to continue")
|
||||
print("AntiCaptcha / Form Test")
|
||||
#browser.go_to("https://www.browserscan.net/bot-detection")
|
||||
#txt = browser.get_text()
|
||||
#browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
||||
browser.go_to("https://home.openweathermap.org/users/sign_up")
|
||||
inputs_visible = browser.get_form_inputs()
|
||||
print("inputs:", inputs_visible)
|
||||
#inputs_fill = ['[q](checked)', '[q](checked)', '[user[username]](mlg)', '[user[email]](mlg.fcu@gmail.com)', '[user[password]](placeholder_P@ssw0rd123)', '[user[password_confirmation]](placeholder_P@ssw0rd123)']
|
||||
#browser.fill_form(inputs_fill)
|
||||
input("press enter to exit")
|
||||
|
||||