bathys 0.6.1__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.
- bathys/__init__.py +8 -0
- bathys/agents/HARNESS-DROPIN.md +50 -0
- bathys/agents/bathys-researcher.md +91 -0
- bathys/agents/skills/bathys-deep-dive/SKILL.md +55 -0
- bathys/agents/skills/bathys-source-audit/SKILL.md +48 -0
- bathys/batch.py +86 -0
- bathys/cache.py +47 -0
- bathys/compose.yaml +22 -0
- bathys/config.py +71 -0
- bathys/core.py +425 -0
- bathys/crawler.py +104 -0
- bathys/distill.py +135 -0
- bathys/doctor.py +139 -0
- bathys/installer.py +424 -0
- bathys/searx.py +149 -0
- bathys/searxng-settings.yml +13 -0
- bathys/server.py +399 -0
- bathys/services.py +263 -0
- bathys-0.6.1.dist-info/METADATA +147 -0
- bathys-0.6.1.dist-info/RECORD +23 -0
- bathys-0.6.1.dist-info/WHEEL +4 -0
- bathys-0.6.1.dist-info/entry_points.txt +4 -0
- bathys-0.6.1.dist-info/licenses/LICENSE +21 -0
bathys/services.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Single-system bootstrap for the SearXNG backend.
|
|
2
|
+
|
|
3
|
+
Order of attempts on a cold start:
|
|
4
|
+
1. already-running instance at BATHYS_SEARXNG_URL (someone else manages it);
|
|
5
|
+
2. container engine (docker compose / podman compose / docker-compose /
|
|
6
|
+
podman-compose) with the bundled compose.yaml — the standard way on a host;
|
|
7
|
+
3. native mode: a private searxng checkout + venv under BATHYS_SEARXNG_HOME,
|
|
8
|
+
cloned and installed on first run, started as a child subprocess — for
|
|
9
|
+
environments without any container engine (plain VPS, CI, containers
|
|
10
|
+
without docker-in-docker).
|
|
11
|
+
|
|
12
|
+
The mode can be forced with BATHYS_START_MODE=docker|native.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
|
|
25
|
+
from .config import Config
|
|
26
|
+
|
|
27
|
+
_REPO_URL = "https://github.com/searxng/searxng"
|
|
28
|
+
_ENGINES = (
|
|
29
|
+
("docker", "compose"),
|
|
30
|
+
("podman", "compose"),
|
|
31
|
+
("docker-compose",),
|
|
32
|
+
("podman-compose",),
|
|
33
|
+
)
|
|
34
|
+
_GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
|
|
35
|
+
|
|
36
|
+
_running_native: "_Native | None" = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def _ping(cfg: Config, http: httpx.AsyncClient, timeout: float = 2.0) -> str | None:
|
|
40
|
+
try:
|
|
41
|
+
r = await http.get(cfg.searxng_url + "/search", params={"q": "ping", "format": "json"}, timeout=timeout)
|
|
42
|
+
if r.status_code == 200:
|
|
43
|
+
return None
|
|
44
|
+
return f"http {r.status_code}"
|
|
45
|
+
except (httpx.HTTPError, ValueError) as e:
|
|
46
|
+
return e.__class__.__name__
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _wait_ready(cfg: Config, http: httpx.AsyncClient, timeout: float) -> str | None:
|
|
50
|
+
loop = asyncio.get_running_loop()
|
|
51
|
+
deadline = loop.time() + timeout
|
|
52
|
+
while True:
|
|
53
|
+
err = await _ping(cfg, http)
|
|
54
|
+
if err is None:
|
|
55
|
+
return None
|
|
56
|
+
if loop.time() > deadline:
|
|
57
|
+
return err
|
|
58
|
+
await asyncio.sleep(1.5)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _run(argv: list[str], cwd: Path | None = None, timeout: float = 120.0) -> tuple[int, str]:
|
|
62
|
+
proc = await asyncio.create_subprocess_exec(
|
|
63
|
+
*argv, cwd=cwd,
|
|
64
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT,
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
out, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout)
|
|
68
|
+
except asyncio.TimeoutError:
|
|
69
|
+
proc.kill()
|
|
70
|
+
return 124, "timeout"
|
|
71
|
+
return proc.returncode or 0, out.decode(errors="replace")[-800:]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _settings_file() -> Path:
|
|
75
|
+
return Path(__file__).resolve().parent / "searxng-settings.yml"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def render_settings(base_text: str, env: dict[str, str] | None = None) -> str:
|
|
79
|
+
"""F-203: optional API-engines injected from env keys — one key enables an
|
|
80
|
+
engine in the generated searxng settings. Native mode writes the rendered
|
|
81
|
+
text; keep the mapping deliberately tiny and explicit."""
|
|
82
|
+
env = env if env is not None else dict(os.environ)
|
|
83
|
+
blocks: list[str] = []
|
|
84
|
+
brave = env.get("BATHYS_ENGINE_BRAVE_KEY", "").strip()
|
|
85
|
+
if brave:
|
|
86
|
+
blocks.append(
|
|
87
|
+
"\nengines:\n"
|
|
88
|
+
" - name: brave\n"
|
|
89
|
+
" engine: brave\n"
|
|
90
|
+
" shortcut: br\n"
|
|
91
|
+
f" api_key: {brave}\n"
|
|
92
|
+
" inactive: false\n"
|
|
93
|
+
)
|
|
94
|
+
text = base_text
|
|
95
|
+
if blocks:
|
|
96
|
+
text = text.rstrip("\n") + "\n" + "".join(blocks)
|
|
97
|
+
return text
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _compose_file() -> Path:
|
|
101
|
+
return Path(__file__).resolve().parent / "compose.yaml"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def _start_containers(cfg: Config) -> None:
|
|
105
|
+
if cfg.start_cmd:
|
|
106
|
+
argv = cfg.start_cmd.split()
|
|
107
|
+
else:
|
|
108
|
+
engine = None
|
|
109
|
+
for cmd in _ENGINES:
|
|
110
|
+
if all(shutil.which(c) for c in cmd):
|
|
111
|
+
engine = cmd
|
|
112
|
+
break
|
|
113
|
+
if engine is None:
|
|
114
|
+
raise FileNotFoundError("no container engine (docker/podman) found")
|
|
115
|
+
argv = [*engine, "-f", str(_compose_file()), "up", "-d"]
|
|
116
|
+
rc, out = await _run(argv, timeout=600)
|
|
117
|
+
if rc != 0:
|
|
118
|
+
raise RuntimeError(out.strip() or f"exit {rc}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class _Native:
|
|
122
|
+
"""searxng checkout + venv under one home dir, run as a child process."""
|
|
123
|
+
|
|
124
|
+
def __init__(self, home: Path, ref: str) -> None:
|
|
125
|
+
self.home = home
|
|
126
|
+
self.repo = home / "repo"
|
|
127
|
+
self.venv = home / "venv"
|
|
128
|
+
self._ref = ref
|
|
129
|
+
self.proc: asyncio.subprocess.Process | None = None
|
|
130
|
+
self.log_path = home / "searxng.log"
|
|
131
|
+
|
|
132
|
+
async def prepare(self) -> None:
|
|
133
|
+
self.home.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
if not (self.repo / "searx" / "webapp.py").is_file():
|
|
135
|
+
rc, out = await _run(
|
|
136
|
+
["git", "clone", "--depth", "1", _REPO_URL, str(self.repo)],
|
|
137
|
+
timeout=300,
|
|
138
|
+
)
|
|
139
|
+
if rc != 0:
|
|
140
|
+
raise RuntimeError(f"git clone searxng failed: {out.strip()}")
|
|
141
|
+
await self._ensure_ref()
|
|
142
|
+
await self._ensure_venv()
|
|
143
|
+
marker = self.venv / ".searxng-reqs-done"
|
|
144
|
+
if not marker.is_file():
|
|
145
|
+
rc, out = await _run(
|
|
146
|
+
[str(self.venv / "bin" / "pip"), "install", "-q", "-r", str(self.repo / "requirements.txt")],
|
|
147
|
+
timeout=900,
|
|
148
|
+
)
|
|
149
|
+
if rc != 0:
|
|
150
|
+
raise RuntimeError(f"searxng requirements install failed: {out.strip()}")
|
|
151
|
+
marker.write_text("ok")
|
|
152
|
+
|
|
153
|
+
async def _ensure_ref(self) -> None:
|
|
154
|
+
"""Pin the checkout to the configured ref (SHA by default); upstream has
|
|
155
|
+
no tags, so a full-SHA pin replaces the old `master` clone (F-302).
|
|
156
|
+
Skips the network when the marker already matches the ref."""
|
|
157
|
+
ref = self._ref
|
|
158
|
+
marker = self.repo / ".bathys-ref"
|
|
159
|
+
if marker.is_file() and marker.read_text().strip() == ref:
|
|
160
|
+
return
|
|
161
|
+
rc, out = await _run(["git", "-C", str(self.repo), "fetch", "-q", "origin"], timeout=300)
|
|
162
|
+
if rc != 0:
|
|
163
|
+
raise RuntimeError(f"git fetch searxng failed: {out.strip()}")
|
|
164
|
+
rc, out = await _run(["git", "-C", str(self.repo), "checkout", "-q", ref], timeout=120)
|
|
165
|
+
if rc != 0:
|
|
166
|
+
raise RuntimeError(f"git checkout {ref} failed: {out.strip()}")
|
|
167
|
+
marker.write_text(ref)
|
|
168
|
+
|
|
169
|
+
async def _ensure_venv(self) -> None:
|
|
170
|
+
"""`python -m venv` needs ensurepip, which minimal debian images lack;
|
|
171
|
+
fall back to venv --without-pip + get-pip.py bootstrap."""
|
|
172
|
+
pip = self.venv / "bin" / "pip"
|
|
173
|
+
if pip.is_file():
|
|
174
|
+
return
|
|
175
|
+
rc, out = await _run([sys.executable, "-m", "venv", str(self.venv)], timeout=120)
|
|
176
|
+
if rc != 0 or not pip.is_file():
|
|
177
|
+
shutil.rmtree(self.venv, ignore_errors=True)
|
|
178
|
+
rc, out = await _run([sys.executable, "-m", "venv", "--without-pip", str(self.venv)], timeout=120)
|
|
179
|
+
if rc != 0:
|
|
180
|
+
raise RuntimeError(f"venv creation failed: {out.strip()}")
|
|
181
|
+
getter = self.home / "get-pip.py"
|
|
182
|
+
rc, out = await _run(["curl", "-sS", _GET_PIP_URL, "-o", str(getter)], timeout=120)
|
|
183
|
+
if rc != 0 or not getter.is_file():
|
|
184
|
+
raise RuntimeError(f"cannot download get-pip.py: {out.strip()}")
|
|
185
|
+
rc, out = await _run([str(self.venv / "bin" / "python"), str(getter), "-q"], timeout=300)
|
|
186
|
+
if rc != 0 or not pip.is_file():
|
|
187
|
+
raise RuntimeError(f"pip bootstrap failed: {out.strip()}")
|
|
188
|
+
await _run([str(pip), "install", "-q", "-U", "pip"], timeout=300)
|
|
189
|
+
|
|
190
|
+
async def start(self, cfg: Config) -> None:
|
|
191
|
+
await self.prepare()
|
|
192
|
+
settings = self.home / "settings.yml"
|
|
193
|
+
settings.write_text(render_settings(_settings_file().read_text()))
|
|
194
|
+
log = open(self.log_path, "ab")
|
|
195
|
+
env = dict(os.environ, SEARXNG_SETTINGS_PATH=str(settings))
|
|
196
|
+
self.proc = await asyncio.create_subprocess_exec(
|
|
197
|
+
str(self.venv / "bin" / "python"), "-m", "searx.webapp",
|
|
198
|
+
cwd=str(self.repo), env=env,
|
|
199
|
+
stdout=log, stderr=asyncio.subprocess.STDOUT,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
async def stop(self) -> None:
|
|
203
|
+
if self.proc is not None and self.proc.returncode is None:
|
|
204
|
+
try:
|
|
205
|
+
self.proc.terminate()
|
|
206
|
+
await asyncio.wait_for(self.proc.wait(), timeout=5)
|
|
207
|
+
except asyncio.TimeoutError:
|
|
208
|
+
self.proc.kill()
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
self.proc = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _native(cfg: Config) -> _Native:
|
|
215
|
+
global _running_native
|
|
216
|
+
if _running_native is None:
|
|
217
|
+
_running_native = _Native(cfg.searxng_home, cfg.searxng_ref)
|
|
218
|
+
return _running_native
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
async def stop_native() -> None:
|
|
222
|
+
global _running_native
|
|
223
|
+
if _running_native is not None:
|
|
224
|
+
await _running_native.stop()
|
|
225
|
+
_running_native = None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
async def ensure_running(cfg: Config, http: httpx.AsyncClient) -> str:
|
|
229
|
+
"""Make sure /search answers at cfg.searxng_url; start a backend if allowed.
|
|
230
|
+
Returns the mode that served the request: external | docker | native."""
|
|
231
|
+
err = await _ping(cfg, http)
|
|
232
|
+
if err is None:
|
|
233
|
+
return "external"
|
|
234
|
+
if not cfg.auto_start:
|
|
235
|
+
raise RuntimeError(
|
|
236
|
+
f"searxng at {cfg.searxng_url} is not answering ({err}); "
|
|
237
|
+
"BATHYS_AUTO_START=0 disables the built-in backend"
|
|
238
|
+
)
|
|
239
|
+
attempts: list[str] = []
|
|
240
|
+
mode = cfg.start_mode
|
|
241
|
+
if mode in ("auto", "docker"):
|
|
242
|
+
try:
|
|
243
|
+
await _start_containers(cfg)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
attempts.append(f"docker: {e}")
|
|
246
|
+
else:
|
|
247
|
+
err2 = await _wait_ready(cfg, http, cfg.startup_timeout)
|
|
248
|
+
if err2 is None:
|
|
249
|
+
return "docker"
|
|
250
|
+
attempts.append(f"docker: not ready ({err2})")
|
|
251
|
+
if mode in ("auto", "native"):
|
|
252
|
+
nat = _native(cfg)
|
|
253
|
+
try:
|
|
254
|
+
if nat.proc is None or nat.proc.returncode is not None:
|
|
255
|
+
await nat.start(cfg)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
attempts.append(f"native: {e}")
|
|
258
|
+
else:
|
|
259
|
+
err3 = await _wait_ready(cfg, http, cfg.startup_timeout)
|
|
260
|
+
if err3 is None:
|
|
261
|
+
return "native"
|
|
262
|
+
attempts.append(f"native: not ready ({err3}; log: {nat.log_path})")
|
|
263
|
+
raise RuntimeError("could not start searxng backend — " + "; ".join(attempts))
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bathys
|
|
3
|
+
Version: 0.6.1
|
|
4
|
+
Summary: Deep-research MCP server: SearXNG metasearch + Crawl4AI extraction, distilled to LLM-lean output
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: crawl4ai>=0.6.0
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: mcp<2,>=1.2.0
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
<p align="center"><img src="docs/assets/banner.svg" alt="Bathys" width="720"></p>
|
|
14
|
+
|
|
15
|
+
# Bathys
|
|
16
|
+
|
|
17
|
+
**Единый локальный поисковый сервис глубокого ресёрча для ИИ-агентов.** Один процесс владеет всем конвейером — поиск → извлечение → дистилляция под запрос → кэш — и всей стратегией ресёрча: что искать, какие страницы читать, сколько вернуть. Агент видит лишь интерфейс из четырёх MCP-инструментов; SearXNG и Crawl4AI работают внутри как сменные движки. Облачных квот и API-ключей нет, LLM внутри нет — дистилляция детерминированная (BM25).
|
|
18
|
+
|
|
19
|
+
Сонар находит координаты, батискаф ныряет за полными текстами, дистиллятор поднимает на палубу только то, что отвечает на вопрос.
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
## ⚡ Quick start
|
|
27
|
+
|
|
28
|
+
Нужны Python ≥ 3.10 и `git`. В каталоге проекта:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/Korrnals/bathys.git && cd bathys
|
|
32
|
+
python3.12 -m venv .venv
|
|
33
|
+
.venv/bin/pip install -e .
|
|
34
|
+
.venv/bin/python -m playwright install chromium # браузер для извлечения
|
|
35
|
+
.venv/bin/python -m unittest discover -s tests # 72 теста без сети, ~0.1 c
|
|
36
|
+
.venv/bin/bathys-doctor # диагностика стека одним запуском
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
В минимальном контейнерном образе без `ensurepip` venv собирается через `get-pip.py` — ветка в [docs/getting-started/install.md](docs/getting-started/install.md). SearXNG поднимать руками не нужно: при первом поиске сервер сам пробует внешний инстанс, затем docker/podman, затем нативный режим ([docs/getting-started/configure.md](docs/getting-started/configure.md)).
|
|
40
|
+
|
|
41
|
+
## 🔌 Подключение к харнессу
|
|
42
|
+
|
|
43
|
+
**Автоматически.** Одна команда найдёт харнессы по стандартным путям, пропишет сервер с бэкапом конфига и (с `--with-agent`) скопирует субагента:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
.venv/bin/bathys install # сначала --dry-run, чтобы увидеть план
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Детектируются zcode, Claude Code, Claude Desktop, Cursor; повторный запуск ничего не меняет — запись идемпотентна. Поддерживаются `--print-config` (готовые блоки для ручной вставки) и `--searxng-home <путь>`.
|
|
50
|
+
|
|
51
|
+
**Вручную.** Bathys — stdio MCP-сервер, конфиг везде один и тот же блок `mcpServers`; от харнесса зависит только файл, в который его кладут. `command` — абсолютный путь к вашему клону (`~` внутри JSON не раскрывается); `BATHYS_SEARXNG_HOME` опциональна.
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"bathys": {
|
|
57
|
+
"command": "/path/to/bathys/.venv/bin/bathys",
|
|
58
|
+
"env": {
|
|
59
|
+
"BATHYS_SEARXNG_HOME": "/path/to/bathys/.runtime/searxng-home"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
| Харнесс | Гайд |
|
|
67
|
+
|---|---|
|
|
68
|
+
| zcode | [docs/integrations/zcode.md](docs/integrations/zcode.md) |
|
|
69
|
+
| Claude Code / Claude Desktop | [docs/integrations/claude-code.md](docs/integrations/claude-code.md) |
|
|
70
|
+
| Cursor | [docs/integrations/cursor.md](docs/integrations/cursor.md) |
|
|
71
|
+
| Любой другой MCP-клиент | [docs/integrations/generic-mcp.md](docs/integrations/generic-mcp.md) |
|
|
72
|
+
|
|
73
|
+
## 🧠 Научить агента работать эффективно
|
|
74
|
+
|
|
75
|
+
Конфиг — только половина дела. Из коробки харнесс получает **instructions-playbook** (матрицу выбора инструментов), **annotations** и **три стратегии-промпта** — `bathys_deep_research`, `bathys_source_audit`, `bathys_fresh_scan`, — так что выбирает инструменты Bathys уже нативно. Сильнее — профиль: субагент [`agents/bathys-researcher.md`](agents/bathys-researcher.md) с двумя скиллами, которому глубокий ресёрч делегируется целиком; для клиентов, не показывающих MCP instructions, — дроп-ин [`agents/HARNESS-DROPIN.md`](agents/HARNESS-DROPIN.md) в `AGENTS.md` / `CLAUDE.md` / `.cursor/rules`.
|
|
76
|
+
|
|
77
|
+
Пошаговая инструкция «из коробки → субагент → дроп-ин» и таблица сигналов футеров — в [«Живых кейсах», раздел C](docs/getting-started/cases.md#c-как-научить-харнесс-работать-с-bathys-эффективно).
|
|
78
|
+
|
|
79
|
+
## 🧭 Ходовые кейсы
|
|
80
|
+
|
|
81
|
+
**Сравнение технологий.** «Сравни SQLite WAL и PostgreSQL под нагрузку — что выбрать в 2026?» → агент вызывает `deep_research`, доуточняет запрос терминами из найденного и верифицирует вывод по двум источникам. Итог: один вызов вместо цепочки «поиск + N чтений», в контекст попадает 7.5k символов вместо ~35k.
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
[bathys: 34 raw hits, top 8 considered · dove 3 pages · 35669 ch fetched → 7508 ch returned · 1.3s]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Аудит спорного утверждения.** «Правда ли, что в X упали замеры?» → стратегия `bathys_source_audit`: пакетное чтение ссылок из обсуждения + кросс-поиск опровержений → вердикт по каждому тезису с URL. Битая ссылка стоит одну строку, а не сорванный вызов.
|
|
88
|
+
|
|
89
|
+
**Свежий срез.** «Что нового в Y за две недели?» → `bathys_fresh_scan`: поиск с `time_range=week` → пакетное чтение → сводка с датами; протухший `cache HIT` лечится одним `refresh=true`.
|
|
90
|
+
|
|
91
|
+
Все кейсы — пользовательские, автономных агентов и эксплуатация — с живыми диалогами и профитом каждого: **[«Живые кейсы» → docs/getting-started/cases.md](docs/getting-started/cases.md)**.
|
|
92
|
+
|
|
93
|
+
## 🛠 Инструменты
|
|
94
|
+
|
|
95
|
+
| Инструмент | Что делает |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `deep_research(query, max_sources=3, …)` | ищет, параллельно читает топ-источники, возвращает слитый дистиллят под запрос. Первый вызов для любого ресёрч-вопроса. |
|
|
98
|
+
| `web_search(query, max_results=8, …)` | ранжированный список ссылок со сниппетами без содержимого страниц; `as_json=true` — чистый JSON для программ. |
|
|
99
|
+
| `read_url(url, query=None, max_chars=8000)` | читает одну страницу; с `query` — только релевантные пассажи. |
|
|
100
|
+
| `read_urls(urls, query=None, total_chars=12000)` | пакетно читает до 10 известных страниц; бюджет делится между успешными, сбой страницы — одна строка, не сорванный вызов. |
|
|
101
|
+
|
|
102
|
+
Поиск сужается общими фильтрами `time_range`, `category`, `engines`, `language`. Живой футер ответа показывает сжатие и кэш: `[bathys: 41 raw hits, top 3 considered · dove 3 pages · 35669 ch fetched → 7508 ch returned · 3.2s]`.
|
|
103
|
+
|
|
104
|
+
## 📊 Экономия токенов
|
|
105
|
+
|
|
106
|
+
Шкала честная и символьная, токены ≈ `chars/4`; каждая цифра взята из футера реального вызова.
|
|
107
|
+
|
|
108
|
+
| Вызов | Из сети | Агенту | Сжатие |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| `web_search` | 37 549 симв. | 1 986 симв. | 18.9× |
|
|
111
|
+
| `read_url` | 17 063 симв. | 2 325 симв. | 7.3× |
|
|
112
|
+
| `deep_research` (3 страницы) | 35 669 симв. | 7 508 симв. | 4.7× |
|
|
113
|
+
|
|
114
|
+
- **Дистилляция под запрос** — пять стадий очистки (JS-рендер → вырезание бойлерплейта → `PruningContentFilter` → схлопывание markdown → BM25-отбор пассажей) с жёсткими бюджетами символов.
|
|
115
|
+
- **Кэш сырца до дистилляции** — SQLite хранит сырой текст, поэтому перечитать страницу под другим углом можно бесплатно и без сети.
|
|
116
|
+
- **Ноль облачных квот** — `deep_research` заменяет цепочку «поиск + N чтений», то есть N+1 списаний квоты, одним локальным вызовом.
|
|
117
|
+
|
|
118
|
+
Методика и пороги — в [docs/operations/metrics.md](docs/operations/metrics.md).
|
|
119
|
+
|
|
120
|
+
## 📚 Документация
|
|
121
|
+
|
|
122
|
+
Хаб с маршрутами «с чего начать» — [docs/index.md](docs/index.md).
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
docs/
|
|
126
|
+
├── index.md # хаб: дерево + три маршрута чтения
|
|
127
|
+
├── getting-started/ # установка · конфигурация · подключение · живые кейсы
|
|
128
|
+
├── integrations/ # zcode · claude-code · cursor · generic-mcp
|
|
129
|
+
├── architecture/ # компоненты · конвейер очистки · потоки данных
|
|
130
|
+
├── contracts/ # инструменты · футеры · модули · конфиг (19 env)
|
|
131
|
+
├── operations/ # runbook · метрики токен-экономии
|
|
132
|
+
├── product/ # хартия · реестр функций · роадмап · конкуренты
|
|
133
|
+
├── adr/ # шесть принятых архитектурных решений
|
|
134
|
+
└── meta/ # стайлгайд · глоссарий
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Вне `docs/`: [agents/](agents/) (субагент `bathys-researcher`, дроп-ин для харнессов) · [tests/](tests/) (72 юнит-теста без сети) · [scripts/](scripts/) (smoke, stdio_check, метрики) · [CHANGELOG.md](CHANGELOG.md).
|
|
138
|
+
|
|
139
|
+
## 📍 Статус
|
|
140
|
+
|
|
141
|
+
**0.6.1.** Выпускная история: v0.2 «Качество выдачи» (ретраи, здоровье движков), v0.3 «Паритет с Tavily» (`read_urls`, JSON-режим), v0.4 «Эксплуатация» (robots-этика, метрики, `bathys-doctor`), v0.5 «Identity & Harness» (репозиционирование, промпты, субагент), v0.6 «Native Install» (`bathys install`) — итоги в [CHANGELOG.md](CHANGELOG.md).
|
|
142
|
+
|
|
143
|
+
Репозиторий: `github.com/Korrnals/bathys`. До 1.0 остаются публикация пакета `bathys` на PyPI (имя свободно, публикация планируется к 1.0) и первый прогон Docker-образа; CI с matrix 3.10–3.12 уже в репозитории.
|
|
144
|
+
|
|
145
|
+
## ⚖️ Лицензия
|
|
146
|
+
|
|
147
|
+
[MIT](LICENSE) — см. поле `license` в `pyproject.toml`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
bathys/__init__.py,sha256=QmtPmLBjgmftKbl0cainuRqm7HoVQoLOSgw1VDS9fUY,277
|
|
2
|
+
bathys/batch.py,sha256=q2NJUeKHHfWGOjpYfxkaSm-S7k1FDeMvoj0A2pbBryQ,3407
|
|
3
|
+
bathys/cache.py,sha256=9OH_w41nfOMPlUgamFPH5o3JHRBL68aUpjQ1M4mwY-E,1588
|
|
4
|
+
bathys/compose.yaml,sha256=InwIIabms5bDBdd9jTrLyzz5A5CiBBuZXJHdZCXdHV8,734
|
|
5
|
+
bathys/config.py,sha256=_IXBayDnDfol9twgSwGSXjg19mXXy4aWzefMtn5vXxM,2913
|
|
6
|
+
bathys/core.py,sha256=ytP3d538YLouR2qgzl0XXqjs5ObayvpMq04VnZNucxw,18819
|
|
7
|
+
bathys/crawler.py,sha256=7xU0ydoswLsl5eHMgBYKABwIla-zsgMy13WUGjYWarw,3600
|
|
8
|
+
bathys/distill.py,sha256=VgLI0dZjeeyPJjbze0AgRedAk5DF317iYrd_rsby19Y,5269
|
|
9
|
+
bathys/doctor.py,sha256=BugLmZ91xyAgkwIhKHwLteywzGhfI-KvFEdmQ1RfuQ8,4821
|
|
10
|
+
bathys/installer.py,sha256=8O9AaNZyXh7pXciu1R-Ua6NLOGxPn76KIZb9RczwbgU,18068
|
|
11
|
+
bathys/searx.py,sha256=oHYxbDoKQFZmo75LObq7qgNLkq_l4eqMVczCdbnmQwg,4416
|
|
12
|
+
bathys/searxng-settings.yml,sha256=veNGKNvX8KhO869gzFUd7RGBLjp2MumUu7xzNn8yjE4,397
|
|
13
|
+
bathys/server.py,sha256=d0V2OhibLeJhRO0hv4HCkck3qF0AyLFp_8JxV8wQzI0,20351
|
|
14
|
+
bathys/services.py,sha256=Cx5uGmywWSWituwLi6vmIAWQzV04g_jSovVpLn0XpOA,9993
|
|
15
|
+
bathys/agents/HARNESS-DROPIN.md,sha256=Nrrx-up8YfpifOheHK6tiKDqBuZsjEwxDXMsJbL8U0I,3830
|
|
16
|
+
bathys/agents/bathys-researcher.md,sha256=ABkbSNmV3WnPpgSF_E8ccAyB-6thqEGDBpHMYHa6KUs,7467
|
|
17
|
+
bathys/agents/skills/bathys-deep-dive/SKILL.md,sha256=9QjHqf6Q-kg6DnV92zZayESkQPU9IZlO-u4W8J9YMN4,3890
|
|
18
|
+
bathys/agents/skills/bathys-source-audit/SKILL.md,sha256=XwpYR5--Z7DZkZjrgk5J-ttwApTM64ESoi6i-_8M9Kc,3418
|
|
19
|
+
bathys-0.6.1.dist-info/METADATA,sha256=k99VYCDX6HQZww8CRkQMPFqNyXko5TA9xRSqrmuumrM,12309
|
|
20
|
+
bathys-0.6.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
21
|
+
bathys-0.6.1.dist-info/entry_points.txt,sha256=7zVSZ8P_wpf6_awxaiOgFAz70skHGZpV8LdT-AWNX_s,120
|
|
22
|
+
bathys-0.6.1.dist-info/licenses/LICENSE,sha256=7JZBFk7EhBscBnceBwIinx-VehGimQDH1hjwWWc-Ulc,1076
|
|
23
|
+
bathys-0.6.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bathys contributors
|
|
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.
|