dotagent-web-research 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dotagent_web_research-1.2.0.dist-info/METADATA +330 -0
- dotagent_web_research-1.2.0.dist-info/RECORD +60 -0
- dotagent_web_research-1.2.0.dist-info/WHEEL +5 -0
- dotagent_web_research-1.2.0.dist-info/entry_points.txt +3 -0
- dotagent_web_research-1.2.0.dist-info/licenses/LICENSE +21 -0
- dotagent_web_research-1.2.0.dist-info/top_level.txt +1 -0
- web_scraper/__init__.py +4 -0
- web_scraper/ai_agent.py +203 -0
- web_scraper/api.py +1549 -0
- web_scraper/api_models.py +773 -0
- web_scraper/api_runtime.py +460 -0
- web_scraper/async_scrapers.py +392 -0
- web_scraper/cli.py +408 -0
- web_scraper/config.py +444 -0
- web_scraper/content_safety.py +189 -0
- web_scraper/duckduckgo_search.py +455 -0
- web_scraper/google_search.py +891 -0
- web_scraper/local_env.py +22 -0
- web_scraper/mcp_server.py +611 -0
- web_scraper/network_safety.py +110 -0
- web_scraper/playwright_scrapers.py +350 -0
- web_scraper/redis_cache.py +215 -0
- web_scraper/research/__init__.py +11 -0
- web_scraper/research/agent.py +2484 -0
- web_scraper/research/citation_verifier.py +236 -0
- web_scraper/research/constants.py +373 -0
- web_scraper/research/llm_client.py +136 -0
- web_scraper/research/models.py +40 -0
- web_scraper/research/profile_collectors.py +464 -0
- web_scraper/research/prompts.py +771 -0
- web_scraper/research/ranking.py +395 -0
- web_scraper/research/retry_utils.py +86 -0
- web_scraper/research/text_utils.py +326 -0
- web_scraper/research/url_utils.py +155 -0
- web_scraper/research_agent.py +13 -0
- web_scraper/scrapers.py +464 -0
- web_scraper/stealth.py +112 -0
- web_scraper/web_tools/__init__.py +5 -0
- web_scraper/web_tools/archive_fetcher.py +183 -0
- web_scraper/web_tools/browser.py +353 -0
- web_scraper/web_tools/budget.py +237 -0
- web_scraper/web_tools/content.py +770 -0
- web_scraper/web_tools/crawl.py +669 -0
- web_scraper/web_tools/crawl_store.py +180 -0
- web_scraper/web_tools/discovery.py +1294 -0
- web_scraper/web_tools/errors.py +63 -0
- web_scraper/web_tools/fetcher.py +407 -0
- web_scraper/web_tools/fields.py +534 -0
- web_scraper/web_tools/metrics.py +72 -0
- web_scraper/web_tools/pagecache.py +102 -0
- web_scraper/web_tools/pdf.py +164 -0
- web_scraper/web_tools/providers.py +71 -0
- web_scraper/web_tools/relevance.py +93 -0
- web_scraper/web_tools/robots.py +71 -0
- web_scraper/web_tools/search.py +321 -0
- web_scraper/web_tools/service.py +2050 -0
- web_scraper/web_tools/settings.py +366 -0
- web_scraper/web_tools/sources.py +388 -0
- web_scraper/web_tools/urls.py +219 -0
- web_scraper/yahoo_search.py +189 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dotagent-web-research
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: MCP server for web search and reading (search, scrape, map, extract, crawl) that Dot, an always-on agent for macOS, uses
|
|
5
|
+
Author-email: Senol Dogan <senoldogan@users.noreply.github.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/senoldogann/web-research-stack
|
|
8
|
+
Project-URL: Repository, https://github.com/senoldogann/web-research-stack.git
|
|
9
|
+
Project-URL: Issues, https://github.com/senoldogann/web-research-stack/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/senoldogann/web-research-stack/tree/main/docs
|
|
11
|
+
Keywords: mcp,model-context-protocol,web-search,web-scraping,crawler,dot,agent
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx>=0.26.0
|
|
23
|
+
Requires-Dist: curl_cffi>=0.7.3
|
|
24
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
25
|
+
Requires-Dist: lxml>=4.9.0
|
|
26
|
+
Requires-Dist: defusedxml>=0.7.1
|
|
27
|
+
Requires-Dist: fastapi>=0.109.0
|
|
28
|
+
Requires-Dist: uvicorn[standard]>=0.27.0
|
|
29
|
+
Requires-Dist: pydantic>=2.5.0
|
|
30
|
+
Requires-Dist: python-dotenv>=1.1.0
|
|
31
|
+
Requires-Dist: click>=8.1.0
|
|
32
|
+
Requires-Dist: tqdm>=4.66.0
|
|
33
|
+
Requires-Dist: pypdf>=5.1.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
38
|
+
Requires-Dist: httpx>=0.26.0; extra == "dev"
|
|
39
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
40
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == "dev"
|
|
41
|
+
Provides-Extra: playwright
|
|
42
|
+
Requires-Dist: playwright>=1.40.0; extra == "playwright"
|
|
43
|
+
Requires-Dist: playwright-stealth>=1.0.6; extra == "playwright"
|
|
44
|
+
Provides-Extra: camoufox
|
|
45
|
+
Requires-Dist: camoufox[geoip]>=0.4.0; extra == "camoufox"
|
|
46
|
+
Provides-Extra: redis
|
|
47
|
+
Requires-Dist: redis[hiredis]>=5.0.0; extra == "redis"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# Web Research Stack / Web Araştırma Yığını
|
|
51
|
+
|
|
52
|
+
<div align="center">
|
|
53
|
+
https://github.com/senoldogann/web-research-stack/raw/main/GITHUB-video.mp4
|
|
54
|
+
<br/>
|
|
55
|
+
<em>2-Minute Demonstration / 2 Dakikalık Tanıtım Videosu</em>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
[English](#english) | [Türkçe](#türkçe)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## English
|
|
63
|
+
|
|
64
|
+
### Overview
|
|
65
|
+
|
|
66
|
+
**Web Scraper** is a powerful, production-ready web scraping and research tool with full-stack capabilities. It combines a Python FastAPI backend with a modern React Next.js frontend, featuring LLM-powered research capabilities with citation support.
|
|
67
|
+
|
|
68
|
+
### Install
|
|
69
|
+
|
|
70
|
+
The MCP server for web search and reading that Dot, the always-on agent for macOS, uses ships as a Python package, `dotagent-web-research` (the import package is `web_scraper`):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install dotagent-web-research
|
|
74
|
+
# or, as a command of its own
|
|
75
|
+
uv tool install dotagent-web-research
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It installs two commands: `web-research-mcp`, the MCP server over stdio (tools `web_search`, `web_scrape`, `web_map`, `web_extract`, `web_crawl_start`, `web_crawl_status`, `web_crawl_results`, `web_crawl_cancel`), and `web-scraper`, the CLI and REST server described below. The headless-browser fallbacks are extras: `pip install "dotagent-web-research[camoufox]"` or `"[playwright]"`.
|
|
79
|
+
|
|
80
|
+
**How Dot uses it.** Dot's package depends on this one, so a fresh Dot install brings it. Dot starts `web-research-mcp` as one of its MCP servers (named `web-research`) and its tools reach the agent as `mcp__web-research__web_search`, `mcp__web-research__web_scrape` and so on. In Dot's `config.toml` it is one table; [docs/web-tools.md](docs/web-tools.md) has it and the settings (`WEB_TOOLS_*`) the server reads.
|
|
81
|
+
|
|
82
|
+
```toml
|
|
83
|
+
[[mcp]]
|
|
84
|
+
name = "web-research"
|
|
85
|
+
command = "web-research-mcp"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Features
|
|
89
|
+
|
|
90
|
+
- **Dual Interface**: CLI commands and REST API
|
|
91
|
+
- **Local Web Research Tools**: HTTP-first multi-angle search with field-weighted BM25, weighted angle consensus, freshness filters, caching and provider cooldown, plus scrape, map, background crawl, and structured extract, exposed over REST and a Dot-ready stdio MCP server (headless-only browser fallback)
|
|
92
|
+
- **LLM Research Tool**: AI-powered web research with citations
|
|
93
|
+
- **Async Architecture**: High-performance concurrent scraping
|
|
94
|
+
- **Production Ready**: Docker, monitoring (Prometheus/Grafana), rate limiting, circuit breaker
|
|
95
|
+
- **Security First**: SSRF protection, input validation, security headers
|
|
96
|
+
- **Multi-language UI**: English and Turkish language support
|
|
97
|
+
- **Streaming Support**: Real-time research progress via Server-Sent Events (SSE)
|
|
98
|
+
- **Code Block Preservation**: Scraper preserves code blocks from source pages with syntax highlighting
|
|
99
|
+
- **Internationalized Status Messages**: Research progress messages adapt to query language (TR/EN)
|
|
100
|
+
- **Resilient JSON Parsing**: 3-stage recovery for LLM synthesis output (strict → repair → retry)
|
|
101
|
+
|
|
102
|
+
### Tech Stack
|
|
103
|
+
|
|
104
|
+
| Component | Technology |
|
|
105
|
+
|-----------|-------------|
|
|
106
|
+
| Backend | Python 3.9+, FastAPI, asyncio |
|
|
107
|
+
| Frontend | React 19, Next.js 15, TypeScript, Tailwind CSS |
|
|
108
|
+
| Database | SQLite (history & caching) |
|
|
109
|
+
| HTTP Client | httpx, curl_cffi |
|
|
110
|
+
| HTML Parsing | BeautifulSoup4, lxml |
|
|
111
|
+
| AI/ML | Ollama, OpenAI API compatible |
|
|
112
|
+
|
|
113
|
+
### Quick Start
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Clone the repository
|
|
117
|
+
git clone https://github.com/senoldogann/web-research-stack.git
|
|
118
|
+
cd web-research-stack
|
|
119
|
+
|
|
120
|
+
# Install dependencies
|
|
121
|
+
pip install -e .
|
|
122
|
+
|
|
123
|
+
# Start the API server
|
|
124
|
+
web-scraper serve
|
|
125
|
+
|
|
126
|
+
# Or use Docker
|
|
127
|
+
docker compose up --build
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Google search stays off your screen
|
|
131
|
+
|
|
132
|
+
Google no longer serves results to plain HTTP clients, and headless Chrome is caught within one request. A headed Chrome gets the real page, so the searcher starts Chrome itself, **minimises its window over CDP before the first search**, and keeps one browser and one tab for the process: ~2 s to start (a brief flash of a small window), then 0.4–1 s per search. If Chrome is missing or refused, **Camoufox** — a patched headless Firefox — takes over.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install -e ".[playwright]" # the Chrome path
|
|
136
|
+
pip install -e ".[camoufox]" && python -m camoufox fetch # the headless fallback
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
- `GOOGLE_ALLOW_VISIBLE_BROWSER=true` — leave the Chrome window on screen (debugging).
|
|
140
|
+
- `GOOGLE_USE_PLAYWRIGHT=false` — skip Google entirely and rely on DuckDuckGo.
|
|
141
|
+
|
|
142
|
+
### API Endpoints
|
|
143
|
+
|
|
144
|
+
| Endpoint | Method | Description |
|
|
145
|
+
|----------|--------|-------------|
|
|
146
|
+
| `/` | GET | API information |
|
|
147
|
+
| `/health` | GET | Health check |
|
|
148
|
+
| `/api/v1/metrics` | GET | Prometheus metrics |
|
|
149
|
+
| `/api/v1/tools` | GET | Tool manifest for LLM |
|
|
150
|
+
| `/api/v1/scrape` | POST | Scrape a single URL |
|
|
151
|
+
| `/api/v1/scrape/batch` | POST | Batch scrape multiple URLs |
|
|
152
|
+
| `/api/v1/tools/web-research` | POST | Run AI-powered research |
|
|
153
|
+
| `/api/v1/tools/web-research/stream` | POST | Stream research progress |
|
|
154
|
+
| `/api/v1/web/search` | POST | Discover URLs with a search adapter |
|
|
155
|
+
| `/api/v1/web/scrape` | POST | Scrape one page to Markdown, metadata, and links |
|
|
156
|
+
| `/api/v1/web/map` | POST | List URLs from sitemaps and page links |
|
|
157
|
+
| `/api/v1/web/extract` | POST | Extract structured fields with provenance |
|
|
158
|
+
| `/api/v1/web/crawl` | POST | Start a background crawl and return its job id |
|
|
159
|
+
| `/api/v1/web/crawl/{job_id}` | GET | Crawl progress and counters |
|
|
160
|
+
| `/api/v1/web/crawl/{job_id}/results` | GET | Paginated crawl results |
|
|
161
|
+
| `/api/v1/web/crawl/{job_id}/cancel` | POST | Cancel a queued or in-flight crawl |
|
|
162
|
+
| `/api/v1/web/stats` | GET | Web tool latency, cache, and browser state |
|
|
163
|
+
| `/api/v1/research` | POST | Legacy research endpoint |
|
|
164
|
+
| `/api/v1/research/stream` | POST | Legacy streaming research |
|
|
165
|
+
|
|
166
|
+
### Example: Research Request
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
curl -X POST "http://127.0.0.1:8000/api/v1/tools/web-research" \
|
|
170
|
+
-H "Content-Type: application/json" \
|
|
171
|
+
-H "X-API-Key: your-api-key" \
|
|
172
|
+
-d '{
|
|
173
|
+
"query": "Latest developments in AI",
|
|
174
|
+
"max_sources": 5,
|
|
175
|
+
"deep_mode": false,
|
|
176
|
+
"provider": "ollama"
|
|
177
|
+
}'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Documentation
|
|
181
|
+
|
|
182
|
+
- [Installation Guide](./docs/installation-tr.md#installation-guide)
|
|
183
|
+
- [User Guide](./docs/user-guide.md#english)
|
|
184
|
+
- [API Documentation](./docs/api.md#english)
|
|
185
|
+
- [LLM Integration](./docs/llm-tool-integration.md)
|
|
186
|
+
- [Local Web Tools](./docs/web-tools.md)
|
|
187
|
+
- [Production Deployment](./docs/production-deployment.md)
|
|
188
|
+
|
|
189
|
+
### License
|
|
190
|
+
|
|
191
|
+
MIT License
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Türkçe
|
|
196
|
+
|
|
197
|
+
### Genel Bakış
|
|
198
|
+
|
|
199
|
+
**Web Scraper**, güçlü ve üretim ortamına hazır bir web kazıma ve araştırma aracıdır. Python FastAPI backend ile modern React Next.js frontend'i birleştirir, alıntılı LLM destekli araştırma özelliklerine sahiptir.
|
|
200
|
+
|
|
201
|
+
### Kurulum
|
|
202
|
+
|
|
203
|
+
Dot'un (macOS için her zaman açık ajan) kullandığı web arama ve okuma MCP sunucusu bir Python paketi olarak gelir: `dotagent-web-research` (içe aktarılan paket `web_scraper`):
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
pip install dotagent-web-research
|
|
207
|
+
# veya kendi başına bir komut olarak
|
|
208
|
+
uv tool install dotagent-web-research
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
İki komut kurar: stdio üzerinden MCP sunucusu `web-research-mcp` ve aşağıda anlatılan CLI ile REST sunucusu `web-scraper`. Headless tarayıcı yedekleri ek pakettir: `pip install "dotagent-web-research[camoufox]"` veya `"[playwright]"`.
|
|
212
|
+
|
|
213
|
+
**Dot bunu nasıl kullanır.** Dot'un paketi buna bağlıdır; yeni bir Dot kurulumu bunu da getirir. Dot `web-research-mcp`'yi MCP sunucularından biri olarak (`web-research` adıyla) başlatır; araçlar ajana `mcp__web-research__web_search`, `mcp__web-research__web_scrape` gibi görünür. Ayrıntı ve ayarlar için [docs/web-tools.md](docs/web-tools.md).
|
|
214
|
+
|
|
215
|
+
### Özellikler
|
|
216
|
+
|
|
217
|
+
- **Çift Arayüz**: CLI komutları ve REST API
|
|
218
|
+
- **Yerel Web Araştırma Araçları**: Alan ağırlıklı BM25, ağırlıklı açı uzlaşması, güncellik filtreleri, önbellek ve sağlayıcı soğuması içeren çok açılı HTTP öncelikli arama; kazıma, haritalama, arka plan taraması ve yapılandırılmış çıkarım; REST ve Dot'a hazır stdio MCP sunucusu (yalnızca headless tarayıcı yedeği)
|
|
219
|
+
- **LLM Araştırma Aracı**: Alıntılı AI destekli web araştırması
|
|
220
|
+
- **Asenkron Mimari**: Yüksek performanslı eşzamanlı kazıma
|
|
221
|
+
- **Üretim Hazır**: Docker, izleme (Prometheus/Grafana), hız sınırlama, circuit breaker
|
|
222
|
+
- **Güvenlik Öncelikli**: SSRF koruması, giriş doğrulama, güvenlik başlıkları
|
|
223
|
+
- **Çok Dilli UI**: İngilizce ve Türkçe dil desteği
|
|
224
|
+
- **Yayın Akışı**: Sunucu Tarafından Gönderilen Olaylar (SSE) ile gerçek zamanlı araştırma ilerlemesi
|
|
225
|
+
- **Kod Bloğu Koruma**: Kaynak sayfalardan kod blokları söz dizimi vurgulamasıyla korunur
|
|
226
|
+
- **Uluslararasılaştırılmış Durum Mesajları**: Araştırma ilerleme mesajları sorgu diline göre uyarlanır (TR/EN)
|
|
227
|
+
- **Dayanıklı JSON Ayrıştırma**: LLM sentez çıktısı için 3 aşamalı kurtarma (katı → onarım → yeniden deneme)
|
|
228
|
+
|
|
229
|
+
### Teknoloji Yığını
|
|
230
|
+
|
|
231
|
+
| Bileşen | Teknoloji |
|
|
232
|
+
|---------|-----------|
|
|
233
|
+
| Backend | Python 3.9+, FastAPI, asyncio |
|
|
234
|
+
| Frontend | React 19, Next.js 15, TypeScript, Tailwind CSS |
|
|
235
|
+
| Veritabanı | SQLite (geçmiş ve önbellek) |
|
|
236
|
+
| HTTP İstemcisi | httpx, curl_cffi |
|
|
237
|
+
| HTML Ayrıştırma | BeautifulSoup4, lxml |
|
|
238
|
+
| AI/ML | Ollama, OpenAI API uyumlu |
|
|
239
|
+
|
|
240
|
+
### Hızlı Başlangıç
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Depoyu klonla
|
|
244
|
+
git clone https://github.com/senoldogann/web-research-stack.git
|
|
245
|
+
cd web-research-stack
|
|
246
|
+
|
|
247
|
+
# Bağımlılıkları yükle
|
|
248
|
+
pip install -e .
|
|
249
|
+
|
|
250
|
+
# API sunucusunu başlat
|
|
251
|
+
web-scraper serve
|
|
252
|
+
|
|
253
|
+
# Veya Docker kullan
|
|
254
|
+
docker compose up --build
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Google araması ekranda görünmez
|
|
258
|
+
|
|
259
|
+
Google artık düz HTTP istemcilerine sonuç döndürmüyor ve headless Chrome tek istekte yakalanıyor. Başlı (headed) Chrome gerçek sayfayı alıyor; bu yüzden arayıcı Chrome'u kendisi başlatıp **penceresini ilk aramadan önce CDP ile küçültüyor** ve süreç boyunca tek tarayıcı + tek sekme kullanıyor: başlatma ~2 sn (küçük bir pencere kısa süre görünür), sonrası arama başına 0,4–1 sn. Chrome yoksa ya da reddederse **Camoufox** — yamalı, headless Firefox — devralıyor.
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
pip install -e ".[playwright]" # Chrome yolu
|
|
263
|
+
pip install -e ".[camoufox]" && python -m camoufox fetch # görünmez yedek
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- `GOOGLE_ALLOW_VISIBLE_BROWSER=true` — Chrome penceresi ekranda kalsın (hata ayıklama).
|
|
267
|
+
- `GOOGLE_USE_PLAYWRIGHT=false` — Google'ı tamamen atla, DuckDuckGo kullan.
|
|
268
|
+
|
|
269
|
+
### API Uç Noktaları
|
|
270
|
+
|
|
271
|
+
| Uç Nokta | Metod | Açıklama |
|
|
272
|
+
|----------|-------|----------|
|
|
273
|
+
| `/` | GET | API bilgileri |
|
|
274
|
+
| `/health` | GET | Sağlık kontrolü |
|
|
275
|
+
| `/api/v1/metrics` | GET | Prometheus metrikleri |
|
|
276
|
+
| `/api/v1/tools` | GET | LLM için araç manifestosu |
|
|
277
|
+
| `/api/v1/scrape` | POST | Tek URL kazıma |
|
|
278
|
+
| `/api/v1/scrape/batch` | POST | Toplu URL kazıma |
|
|
279
|
+
| `/api/v1/tools/web-research` | POST | AI destekli araştırma |
|
|
280
|
+
| `/api/v1/tools/web-research/stream` | POST | Araştırma ilerlemesi yayını |
|
|
281
|
+
| `/api/v1/web/search` | POST | Arama adaptörüyle URL keşfi |
|
|
282
|
+
| `/api/v1/web/scrape` | POST | Tek sayfayı Markdown, üst veri ve bağlantılara dönüştür |
|
|
283
|
+
| `/api/v1/web/map` | POST | Site haritaları ve sayfa bağlantılarından URL listele |
|
|
284
|
+
| `/api/v1/web/extract` | POST | Kaynak bilgisiyle yapılandırılmış alan çıkar |
|
|
285
|
+
| `/api/v1/web/crawl` | POST | Arka plan taraması başlat, iş kimliğini hemen döndür |
|
|
286
|
+
| `/api/v1/web/crawl/{job_id}` | GET | Tarama ilerlemesi ve sayaçları |
|
|
287
|
+
| `/api/v1/web/crawl/{job_id}/results` | GET | Sayfalanmış tarama sonuçları |
|
|
288
|
+
| `/api/v1/web/crawl/{job_id}/cancel` | POST | Sıradaki veya sürmekte olan taramayı iptal et |
|
|
289
|
+
| `/api/v1/web/stats` | GET | Web aracı gecikmesi, önbellek ve tarayıcı durumu |
|
|
290
|
+
| `/api/v1/research` | POST | Eski araştırma uç noktası |
|
|
291
|
+
| `/api/v1/research/stream` | POST | Eski yayın akışı araştırması |
|
|
292
|
+
|
|
293
|
+
### Örnek: Araştırma İsteği
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
curl -X POST "http://127.0.0.1:8000/api/v1/tools/web-research" \
|
|
297
|
+
-H "Content-Type: application/json" \
|
|
298
|
+
-H "X-API-Key: sizin-api-anahtariniz" \
|
|
299
|
+
-d '{
|
|
300
|
+
"query": "Yapay zeka son gelişmeler",
|
|
301
|
+
"max_sources": 5,
|
|
302
|
+
"deep_mode": false,
|
|
303
|
+
"provider": "ollama"
|
|
304
|
+
}'
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Dokümantasyon
|
|
308
|
+
|
|
309
|
+
- [Kurulum Rehberi](./docs/installation-tr.md#kurulum-rehberi)
|
|
310
|
+
- [Kullanım Kılavuzu](./docs/user-guide.md#türkçe)
|
|
311
|
+
- [API Dokümantasyonu](./docs/api.md#türkçe)
|
|
312
|
+
- [LLM Entegrasyonu](./docs/llm-tool-integration.md)
|
|
313
|
+
- [Yerel Web Araçları](./docs/web-tools.md)
|
|
314
|
+
- [Üretim Dağıtımı](./docs/production-deployment.md)
|
|
315
|
+
|
|
316
|
+
### Lisans
|
|
317
|
+
|
|
318
|
+
MIT Lisansı
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Contributing / Katkıda Bulunma
|
|
323
|
+
|
|
324
|
+
Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md).
|
|
325
|
+
|
|
326
|
+
Katkılarınızı bekliyoruz! Lütfen [Katkı Rehberimizi](./CONTRIBUTING.md) okuyun.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
© 2026 Senol Dogan. MIT License.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
dotagent_web_research-1.2.0.dist-info/licenses/LICENSE,sha256=97vp88nO0uKuooYG2QSz0rNfjuhHcMMij_dtgZn2guk,1068
|
|
2
|
+
web_scraper/__init__.py,sha256=GJbvh_9N3M0uqnw8QgHhJoyGWYhD6JjLQLf43ZJF-qY,123
|
|
3
|
+
web_scraper/ai_agent.py,sha256=_Kks-ps0vsw8R1tH9oFz2QybI8IBn8makEpgXAB5Hyk,6923
|
|
4
|
+
web_scraper/api.py,sha256=U0oemES_Llqx9gh9DOvYF_bkXT5OeQFFYfTDwpvBCW4,59546
|
|
5
|
+
web_scraper/api_models.py,sha256=ZEtQcPYrs5GrHt_pFPlzcDDnDy1Y3NVCxlWcDPZr1tM,25917
|
|
6
|
+
web_scraper/api_runtime.py,sha256=17q8pyNjZJqHMb_5Q2uolCXM2zAbF5y7UcuLQkB3wIk,16352
|
|
7
|
+
web_scraper/async_scrapers.py,sha256=C2_Sc1aILlqj1goeow1vUKxnoOHZkodK-LX8ooOtnLQ,14061
|
|
8
|
+
web_scraper/cli.py,sha256=CEbcafI1aDNq6lH6RLZZOaK0tLIwFh6WO_wp9-uBarc,11555
|
|
9
|
+
web_scraper/config.py,sha256=wWmcFM2nglJ_7mG_C27x4t-z-krZoCfQ5UYMT951s90,19097
|
|
10
|
+
web_scraper/content_safety.py,sha256=zs6BlWBbLRW7j-AU3VZ5W4N4ku8Xa-XpgFogLIoYR4o,6591
|
|
11
|
+
web_scraper/duckduckgo_search.py,sha256=sA4sDuFPE54M4Zbw4zW0qxTFfrJXYslzn4a-0G2Ua1Q,16599
|
|
12
|
+
web_scraper/google_search.py,sha256=oA7n5pUYJXzFPAerHElZGyGfhcowbo5Uz2it-wUvLag,33781
|
|
13
|
+
web_scraper/local_env.py,sha256=JugcZkJ5MjNhnAq7gukl1JnWFbAKPgpTtQCHtCktnMQ,951
|
|
14
|
+
web_scraper/mcp_server.py,sha256=7zZLcR3F8ICM6VTgLNgJk6RRYrnC1DOXvG6Sn63aXwE,24947
|
|
15
|
+
web_scraper/network_safety.py,sha256=xB-4cwjZn6TEt4JZj6YLppLQ_q0zcHePW-bBDPE3neU,3557
|
|
16
|
+
web_scraper/playwright_scrapers.py,sha256=eyMGPcUkxTKrhaVcIqDDtjsJnli8Da0T-Awaay1QnOc,12309
|
|
17
|
+
web_scraper/redis_cache.py,sha256=VoAlwxAchJ5remLIkS9Q_BQ4uN7jWrNGfkLMAcrnlQM,7610
|
|
18
|
+
web_scraper/research_agent.py,sha256=1r9cuvRxv5FQzj3GVrKu7lfu5Z9ju33UEk6mW44SMQo,474
|
|
19
|
+
web_scraper/scrapers.py,sha256=tbp16ZTw_3770yMyuYmc3TPm5k_eKUmIhRNGVm_5Zgc,15968
|
|
20
|
+
web_scraper/stealth.py,sha256=mfdb2x5OIteHVPdon11b7yU8qiX6REAabWfQJ5oe4jc,4746
|
|
21
|
+
web_scraper/yahoo_search.py,sha256=YpBHIOFUYCWJAqHE6hNKsIsiBiB_vDap-2e4wp7LzSQ,8164
|
|
22
|
+
web_scraper/research/__init__.py,sha256=UpZ9S3JM7OSAK5HRp5cfjaYtBAry70hZR1ipCduUSms,359
|
|
23
|
+
web_scraper/research/agent.py,sha256=YFl5M5p1uZMYzSFameU3s38yhhEzjxxv7exBsyrVkAg,96949
|
|
24
|
+
web_scraper/research/citation_verifier.py,sha256=xLA3WOT4Kcmmw5JrifFFNhkRzd9TzhMLMfhLMv482e4,6349
|
|
25
|
+
web_scraper/research/constants.py,sha256=8R2UTpW5bp7KEEg9ULnJfBneQpA6NtufwqgMBbUVbaY,13903
|
|
26
|
+
web_scraper/research/llm_client.py,sha256=GFy1C9a2j8fTrKkZfqbIoIfPr5OGaSJbcoVK_Dcex1A,5151
|
|
27
|
+
web_scraper/research/models.py,sha256=BcDlAwv0g6KogaO6lRgV5M14qaLvX9w7p-NkTQXhjPM,1185
|
|
28
|
+
web_scraper/research/profile_collectors.py,sha256=kEiIvl7znmdnUy6wJIbSxhP2EGc8oWXcVd9_B0OC7JA,17406
|
|
29
|
+
web_scraper/research/prompts.py,sha256=0kRRd83_XQAYH9APo8cuaibS2k4aCvbq2MJfSXNGb34,34855
|
|
30
|
+
web_scraper/research/ranking.py,sha256=D9rnP4HooMIOMhcg1bPV49Nn3Otex-bL4B-DnGbVEXs,13220
|
|
31
|
+
web_scraper/research/retry_utils.py,sha256=wwE5W8mT5HF4_Es-uBXXe_RC1mbq3lehLfpCOlpDhDU,2527
|
|
32
|
+
web_scraper/research/text_utils.py,sha256=VTzUPXnyRSs7W_u762rN0bJlMUEBQFg6f686heyBaWY,8999
|
|
33
|
+
web_scraper/research/url_utils.py,sha256=tJfvCr6SByhmwtt_70yFIPCso3HCvxcotKj2XIveBWo,4223
|
|
34
|
+
web_scraper/web_tools/__init__.py,sha256=oKm85LqRlpR-Qf2CqtyV5Yic_ebZUQVUH1fCV690cf8,231
|
|
35
|
+
web_scraper/web_tools/archive_fetcher.py,sha256=8_5VGKijga8ugTuelCHwFEJPRZhaJDOMuyT8RWj0mF8,7518
|
|
36
|
+
web_scraper/web_tools/browser.py,sha256=mpyA8LDWhaZp0FbxMO-72xODdfOZqG11K9dsbM0k49k,12587
|
|
37
|
+
web_scraper/web_tools/budget.py,sha256=dlWTkU5-sIRirNedMkAv8bFURQIBeAMMWISkwNRcgMI,8603
|
|
38
|
+
web_scraper/web_tools/content.py,sha256=mzCxfo2RiJEgJmTTIO_hlwF2te445qVAfuRilGLUWc4,27268
|
|
39
|
+
web_scraper/web_tools/crawl.py,sha256=xlrgN-ea829Hu4i-CwwXz3WYKHgWgV681yFugCIR1v0,25742
|
|
40
|
+
web_scraper/web_tools/crawl_store.py,sha256=ZIabI-JJDk022NcB0wGP3H7CEK7GU8dOGoUHaOSgkrM,6700
|
|
41
|
+
web_scraper/web_tools/discovery.py,sha256=pnKBrz3pBIeLNCioqta3g7dZ4ph04YLngWnGD4iH0tM,41838
|
|
42
|
+
web_scraper/web_tools/errors.py,sha256=iRpiNT3oC5b3EQPKKK6p0wg0oeP141gILZxxzirZ4Uw,1627
|
|
43
|
+
web_scraper/web_tools/fetcher.py,sha256=eq3rtW-YjyXBBc0ODp5BLyeB20InDE4d6xkYdsgj3KA,15857
|
|
44
|
+
web_scraper/web_tools/fields.py,sha256=-aKmYI2QxQSm8LCF5oCN-nV_V6KEjET9pEbY5hhLUG0,18603
|
|
45
|
+
web_scraper/web_tools/metrics.py,sha256=BHXrGU1T99RcIV0UtlPBy5lCiaNK4VlzlNeRrX4bs08,2523
|
|
46
|
+
web_scraper/web_tools/pagecache.py,sha256=99kaYf1UCIIDuodU_ejvulfRx4LLjIp3SpQpKtfQMgk,3693
|
|
47
|
+
web_scraper/web_tools/pdf.py,sha256=RMHmEvGpGb4AXYwf1BHxr0xTT3ZHi0jpBa4vKOFOmw4,5811
|
|
48
|
+
web_scraper/web_tools/providers.py,sha256=SYNyz0t1yeicphiTYOdh8QAkPTVAYLtJR9c24cWxi6g,2490
|
|
49
|
+
web_scraper/web_tools/relevance.py,sha256=u3g3YIjpMHL1xqA95ufAoTgiUo55iaFTcPFK2f9kRBE,3398
|
|
50
|
+
web_scraper/web_tools/robots.py,sha256=yJOxRbuRRS-TY1gi8obFdF5Rtinwxn7aRrcqn0aubiU,2500
|
|
51
|
+
web_scraper/web_tools/search.py,sha256=qhVrhfU4N2Cr4NcXcP6SunSqIdmjV-5p1wxKTWTTDb8,10554
|
|
52
|
+
web_scraper/web_tools/service.py,sha256=7Bg52eiGVNMEbC1Azkb2cYZLq-hHCfzM6lb3D7kiOEc,81863
|
|
53
|
+
web_scraper/web_tools/settings.py,sha256=guMDA0Kqst1UWycbH9ysy3vhTpugBf_qB1OtPhFi1oc,17335
|
|
54
|
+
web_scraper/web_tools/sources.py,sha256=tyDCWOrr2ES2UZwzBGoQZjUJQyfW3LqfxG8jTZDI2Ss,9160
|
|
55
|
+
web_scraper/web_tools/urls.py,sha256=zgOVmmgZsSHrfTZJ4X7QcyCSNiixA6r-CIUV1P06DE8,7337
|
|
56
|
+
dotagent_web_research-1.2.0.dist-info/METADATA,sha256=SgndqzxZg7I-s9yzyICcfh3CK3O3plIoeoUllZTlR44,14717
|
|
57
|
+
dotagent_web_research-1.2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
58
|
+
dotagent_web_research-1.2.0.dist-info/entry_points.txt,sha256=xdDR2nX0ek-BRCuNK3hDbn8v4rQm95Hf9ZlptbdBuLc,100
|
|
59
|
+
dotagent_web_research-1.2.0.dist-info/top_level.txt,sha256=0ynyaW_79oDijS6kQjJfk67X11tO-Qm6XfuhhuziCkk,12
|
|
60
|
+
dotagent_web_research-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Senol Dogan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
web_scraper
|
web_scraper/__init__.py
ADDED
web_scraper/ai_agent.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""AI Agent for natural language web scraping commands using Ollama."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from web_scraper.config import config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OllamaAgent:
|
|
13
|
+
"""AI agent that processes natural language commands for web scraping."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: Optional[str] = None, host: Optional[str] = None):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the Ollama agent.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
model: Ollama model name (default: gpt-oss:120b-cloud)
|
|
21
|
+
host: Ollama API host (default: http://localhost:11434)
|
|
22
|
+
"""
|
|
23
|
+
self.model = model or config.default_research_model
|
|
24
|
+
self.host = host or config.ollama_host
|
|
25
|
+
self.api_url = f"{self.host}/api/generate"
|
|
26
|
+
|
|
27
|
+
async def process_command(self, command: str) -> dict:
|
|
28
|
+
"""
|
|
29
|
+
Process a natural language command and return scraping parameters.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
command: Natural language command (e.g., "scrape anthropic site and get me the last 10 news")
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary with URL, scraping mode, and filters
|
|
36
|
+
"""
|
|
37
|
+
prompt = self._create_prompt(command)
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
async with httpx.AsyncClient() as client:
|
|
41
|
+
response = await client.post(
|
|
42
|
+
self.api_url,
|
|
43
|
+
json={
|
|
44
|
+
"model": self.model,
|
|
45
|
+
"prompt": prompt,
|
|
46
|
+
"stream": False,
|
|
47
|
+
},
|
|
48
|
+
timeout=config.research_source_selection_timeout_seconds,
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
|
|
52
|
+
result = response.json()
|
|
53
|
+
ai_response = result.get("response", "")
|
|
54
|
+
|
|
55
|
+
# Parse the AI response
|
|
56
|
+
return self._parse_response(ai_response, command)
|
|
57
|
+
|
|
58
|
+
except Exception:
|
|
59
|
+
# Fallback to manual parsing if AI fails
|
|
60
|
+
return self._fallback_parse(command)
|
|
61
|
+
|
|
62
|
+
def _create_prompt(self, command: str) -> str:
|
|
63
|
+
"""Create a prompt for the AI model."""
|
|
64
|
+
return f"""You are a web scraping assistant. Extract the following information from the user's command and return ONLY a JSON object:
|
|
65
|
+
|
|
66
|
+
User command: "{command}"
|
|
67
|
+
|
|
68
|
+
Extract:
|
|
69
|
+
1. url: The website URL to scrape (add https:// if missing)
|
|
70
|
+
2. mode: "playwright" if JavaScript-heavy site (like React/Vue/Angular) or protected by Cloudflare, otherwise "basic"
|
|
71
|
+
3. max_items: Number of items to extract (news, articles, etc.) if specified
|
|
72
|
+
4. filters: Any specific content to look for (news, blog posts, products, etc.)
|
|
73
|
+
|
|
74
|
+
Return ONLY this JSON format:
|
|
75
|
+
{{
|
|
76
|
+
"url": "https://example.com",
|
|
77
|
+
"mode": "basic",
|
|
78
|
+
"max_items": 10,
|
|
79
|
+
"filters": "news"
|
|
80
|
+
}}
|
|
81
|
+
|
|
82
|
+
If URL cannot be determined, use empty string. If max_items not specified, use 0 (all)."""
|
|
83
|
+
|
|
84
|
+
def _parse_response(self, response: str, original_command: str) -> dict:
|
|
85
|
+
"""Parse the AI response into a structured dictionary."""
|
|
86
|
+
try:
|
|
87
|
+
# Try to extract JSON from the response
|
|
88
|
+
json_match = re.search(r"\{[^}]*\}", response, re.DOTALL)
|
|
89
|
+
if json_match:
|
|
90
|
+
data = json.loads(json_match.group())
|
|
91
|
+
return {
|
|
92
|
+
"url": data.get("url", ""),
|
|
93
|
+
"mode": data.get("mode", "basic"),
|
|
94
|
+
"max_items": data.get("max_items", 0),
|
|
95
|
+
"filters": data.get("filters", ""),
|
|
96
|
+
"original_command": original_command,
|
|
97
|
+
}
|
|
98
|
+
except json.JSONDecodeError:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
return self._fallback_parse(original_command)
|
|
102
|
+
|
|
103
|
+
def _fallback_parse(self, command: str) -> dict:
|
|
104
|
+
"""Fallback parser when AI is not available."""
|
|
105
|
+
# Extract URL from command
|
|
106
|
+
url_pattern = r"(https?://[^\s]+|www\.[^\s]+|[a-zA-Z0-9-]+\.(com|org|net|io|dev|co)[^\s]*)"
|
|
107
|
+
url_match = re.search(url_pattern, command)
|
|
108
|
+
|
|
109
|
+
url = ""
|
|
110
|
+
if url_match:
|
|
111
|
+
url = url_match.group(1)
|
|
112
|
+
if not url.startswith("http"):
|
|
113
|
+
url = f"https://{url}"
|
|
114
|
+
|
|
115
|
+
# Extract number
|
|
116
|
+
number_match = re.search(r"(\d+)\s*(news|articles|posts|items)?", command, re.IGNORECASE)
|
|
117
|
+
max_items = int(number_match.group(1)) if number_match else 0
|
|
118
|
+
|
|
119
|
+
# Determine if playwright needed
|
|
120
|
+
mode = (
|
|
121
|
+
"playwright"
|
|
122
|
+
if any(
|
|
123
|
+
word in command.lower() for word in ["javascript", "react", "dynamic", "cloudflare"]
|
|
124
|
+
)
|
|
125
|
+
else "basic"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Extract filters
|
|
129
|
+
filters = ""
|
|
130
|
+
if "news" in command.lower():
|
|
131
|
+
filters = "news"
|
|
132
|
+
elif "blog" in command.lower():
|
|
133
|
+
filters = "blog"
|
|
134
|
+
elif "article" in command.lower():
|
|
135
|
+
filters = "articles"
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"url": url,
|
|
139
|
+
"mode": mode,
|
|
140
|
+
"max_items": max_items,
|
|
141
|
+
"filters": filters,
|
|
142
|
+
"original_command": command,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async def summarize_content(self, content: str, filters: str = "", max_items: int = 0) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Summarize scraped content using AI.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
content: The scraped content
|
|
151
|
+
filters: Type of content to focus on
|
|
152
|
+
max_items: Maximum number of items to extract
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Formatted summary
|
|
156
|
+
"""
|
|
157
|
+
# Truncate content if too long
|
|
158
|
+
if len(content) > config.research_deep_content_limit_chars:
|
|
159
|
+
content = content[: config.research_deep_content_limit_chars] + "..."
|
|
160
|
+
|
|
161
|
+
prompt = f"""Analyze this website content and extract the key information{f" about {filters}" if filters else ""}.
|
|
162
|
+
|
|
163
|
+
{f"Extract up to {max_items} items if applicable." if max_items > 0 else ""}
|
|
164
|
+
|
|
165
|
+
Content:
|
|
166
|
+
{content}
|
|
167
|
+
|
|
168
|
+
Format your response as a clean, structured output with:
|
|
169
|
+
1. Main topics/sections found
|
|
170
|
+
2. Key information points
|
|
171
|
+
3. Any lists or enumerated items
|
|
172
|
+
|
|
173
|
+
Keep it concise and well-organized."""
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
async with httpx.AsyncClient() as client:
|
|
177
|
+
response = await client.post(
|
|
178
|
+
self.api_url,
|
|
179
|
+
json={
|
|
180
|
+
"model": self.model,
|
|
181
|
+
"prompt": prompt,
|
|
182
|
+
"stream": False,
|
|
183
|
+
},
|
|
184
|
+
timeout=config.research_synthesis_timeout_seconds,
|
|
185
|
+
)
|
|
186
|
+
response.raise_for_status()
|
|
187
|
+
|
|
188
|
+
result = response.json()
|
|
189
|
+
return result.get("response", "Could not generate summary.")
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
return f"Error generating summary: {str(e)}"
|
|
193
|
+
|
|
194
|
+
def is_available(self) -> bool:
|
|
195
|
+
"""Check if Ollama is running. Stays sync — called before the event loop."""
|
|
196
|
+
try:
|
|
197
|
+
response = httpx.get(
|
|
198
|
+
f"{self.host}/api/tags",
|
|
199
|
+
timeout=min(5.0, config.research_planning_timeout_seconds),
|
|
200
|
+
)
|
|
201
|
+
return response.status_code == 200
|
|
202
|
+
except Exception:
|
|
203
|
+
return False
|