diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 3713b14..34f1cbf 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -54,9 +54,11 @@ Autonomous personal assistant with a multi-channel gateway. Three-tier model rou 3. 202 Accepted immediately 4. Background: run_agent_task(message, session_id, channel) 5. Parallel IO (asyncio.gather): - a. _fetch_urls_from_message() — Crawl4AI fetches any URLs in message - b. _retrieve_memories() — openmemory semantic search for context -6. router.route() with enriched history (url_context + memories as system msgs) + a. _fetch_urls_from_message() — Crawl4AI fetches any URLs in message + b. _retrieve_memories() — openmemory semantic search for context + c. _fast_tool_runner.run_matching() — FastTools (weather, commute) if pattern matches +6. router.route() with enriched history (url_context + fast_context + memories) + - fast tool match → force medium (real-time data, no point routing to light) - if URL content fetched and tier=light → upgrade to medium 7. Invoke agent for tier with url_context + memories in system prompt 8. Token streaming: @@ -90,6 +92,33 @@ Adolf uses LangChain's tool interface but only the complex agent actually invoke Complex tier is locked out unless the message starts with `/think` — any LLM classification of "complex" is downgraded to medium. +## Fast Tools (`fast_tools.py`) + +Pre-flight tools that run concurrently with URL fetch and memory retrieval before any LLM call. Each tool has two methods: +- `matches(message) → bool` — regex classifier; also used by `Router` to force medium tier +- `run(message) → str` — async fetch returning a context block injected into system prompt + +`FastToolRunner` holds all tools. `any_matches()` is called by the Router at step 0a; `run_matching()` is called in the pre-flight `asyncio.gather` in `run_agent_task()`. + +| Tool | Pattern | Source | Context returned | +|------|---------|--------|-----------------| +| `WeatherTool` | weather/forecast/temperature/snow/rain | SearXNG `"погода Балашиха сейчас"` | Current conditions in °C from Russian weather sites | +| `CommuteTool` | commute/traffic/arrival/пробки | `routecheck:8090/api/route` (Yandex Routing API) | Drive time with/without traffic, Balashikha→Moscow | + +**To add a new fast tool:** subclass `FastTool` in `fast_tools.py`, implement `name`/`matches`/`run`, add an instance to `_fast_tool_runner` in `agent.py`. + +## routecheck Service (`routecheck/`) + +Local web service on port 8090. Exists because Yandex Routing API free tier requires a web UI that uses the API. + +**Web UI** (`http://localhost:8090`): PIL-generated arithmetic captcha → lat/lon form → travel time result. + +**Internal API**: `GET /api/route?from=lat,lon&to=lat,lon&token=ROUTECHECK_TOKEN` — bypasses captcha, used by `CommuteTool`. The `ROUTECHECK_TOKEN` shared secret is set in `.env` and passed to both `routecheck` and `deepagents` containers. + +Yandex API calls are routed through the host HTTPS proxy (`host.docker.internal:56928`) since the container has no direct external internet access. + +**Requires** `.env`: `YANDEX_ROUTING_KEY` (free from `developer.tech.yandex.ru`) + `ROUTECHECK_TOKEN`. + ## Crawl4AI Integration Crawl4AI runs as a Docker service (`crawl4ai:11235`) providing JS-rendered, bot-bypass page fetching. @@ -135,20 +164,24 @@ Conversation history is keyed by session_id (5-turn buffer). ``` adolf/ -├── docker-compose.yml Services: bifrost, deepagents, openmemory, grammy, crawl4ai, cli (profile:tools) +├── docker-compose.yml Services: bifrost, deepagents, openmemory, grammy, crawl4ai, routecheck, cli ├── Dockerfile deepagents container (Python 3.12) ├── Dockerfile.cli CLI container (python:3.12-slim + rich) -├── agent.py FastAPI gateway, run_agent_task, Crawl4AI pre-fetch, memory pipeline, /stream/ SSE +├── agent.py FastAPI gateway, run_agent_task, Crawl4AI pre-fetch, fast tools, memory pipeline +├── fast_tools.py FastTool base, FastToolRunner, WeatherTool, CommuteTool ├── channels.py Channel registry + deliver() + pending_replies -├── router.py Router class — regex + LLM tier classification +├── router.py Router class — regex + LLM tier classification, FastToolRunner integration ├── vram_manager.py VRAMManager — flush/prewarm/poll Ollama VRAM ├── agent_factory.py _DirectModel (medium) / create_deep_agent (complex) ├── cli.py Interactive CLI REPL — Rich Live streaming + Markdown render ├── wiki_research.py Batch wiki research pipeline (uses /message + SSE) +├── .env TELEGRAM_BOT_TOKEN, ROUTECHECK_TOKEN, YANDEX_ROUTING_KEY (not committed) +├── routecheck/ +│ ├── app.py FastAPI: image captcha + /api/route Yandex proxy +│ └── Dockerfile ├── tests/ │ ├── integration/ Standalone integration test scripts (common.py + test_*.py) │ └── use_cases/ Claude Code skill markdown files — Claude acts as user + evaluator -├── .env TELEGRAM_BOT_TOKEN (not committed) ├── openmemory/ │ ├── server.py FastMCP + mem0: add_memory, search_memory, get_all_memories │ └── Dockerfile diff --git a/CLAUDE.md b/CLAUDE.md index f7271d9..a0bdbac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,10 +46,12 @@ Channel adapter → POST /message {text, session_id, channel, user_id} → 202 Accepted (immediate) → background: run_agent_task() → asyncio.gather( - _fetch_urls_from_message() ← Crawl4AI, concurrent - _retrieve_memories() ← openmemory search, concurrent + _fetch_urls_from_message() ← Crawl4AI, concurrent + _retrieve_memories() ← openmemory search, concurrent + _fast_tool_runner.run_matching() ← FastTools (weather, commute), concurrent ) → router.route() → tier decision (light/medium/complex) + fast tool match → force medium if URL content fetched → upgrade light→medium → invoke agent for tier via Bifrost (url_context + memories in system prompt) deepagents:8000 → bifrost:8080/v1 → ollama:11436 @@ -112,6 +114,7 @@ Session IDs: `tg-` for Telegram, `cli-` for CLI. Conversation | `openmemory` | 8765 | FastMCP server + mem0 memory tools (Qdrant-backed) | | `grammy` | 3001 | grammY Telegram bot + `/send` HTTP endpoint | | `crawl4ai` | 11235 | JS-rendered page fetching | +| `routecheck` | 8090 | Local routing web service — image captcha UI + Yandex Routing API backend | | `cli` | — | Interactive CLI container (`profiles: [tools]`), Rich streaming display | External (from `openai/` stack, host ports): @@ -134,6 +137,25 @@ Crawl4AI is embedded at all levels of the pipeline: MCP tools from openmemory (`add_memory`, `search_memory`, `get_all_memories`) are **excluded** from agent tools — memory management is handled outside the agent loop. +### Fast Tools (`fast_tools.py`) + +Pre-flight tools that run before the LLM in the `asyncio.gather` alongside URL fetch and memory retrieval. Each tool has a regex `matches()` classifier and an async `run()` that returns a context string injected into the system prompt. The router uses `FastToolRunner.any_matches()` to force medium tier when a tool matches. + +| Tool | Trigger | Data source | +|------|---------|-------------| +| `WeatherTool` | weather/forecast/temperature keywords | SearXNG query `"погода Балашиха сейчас"` — Russian sources return °C | +| `CommuteTool` | commute/traffic/arrival time keywords | `routecheck:8090/api/route` — Yandex Routing API, Balashikha→Moscow center | + +To add a new fast tool: subclass `FastTool` in `fast_tools.py`, add an instance to `_fast_tool_runner` in `agent.py`. + +### `routecheck` service (`routecheck/app.py`) + +Local web service that exposes Yandex Routing API behind an image captcha. Two access paths: +- **Web UI** (`localhost:8090`): solve PIL-generated arithmetic captcha → query any two lat/lon points +- **Internal API**: `GET /api/route?from=lat,lon&to=lat,lon&token=ROUTECHECK_TOKEN` — bypasses captcha, used by `CommuteTool` + +Requires `.env`: `YANDEX_ROUTING_KEY` (free tier from `developer.tech.yandex.ru`) and `ROUTECHECK_TOKEN`. The container routes Yandex API calls through the host HTTPS proxy (`host.docker.internal:56928`). + ### Medium vs Complex agent | Agent | Builder | Speed | Use case | @@ -143,7 +165,9 @@ MCP tools from openmemory (`add_memory`, `search_memory`, `get_all_memories`) ar ### Key files -- `agent.py` — FastAPI app, lifespan wiring, `run_agent_task()`, Crawl4AI pre-fetch, memory pipeline, all endpoints +- `agent.py` — FastAPI app, lifespan wiring, `run_agent_task()`, Crawl4AI pre-fetch, fast tools, memory pipeline, all endpoints +- `fast_tools.py` — `FastTool` base class, `FastToolRunner`, `WeatherTool`, `CommuteTool` +- `routecheck/app.py` — captcha UI + `/api/route` Yandex proxy - `bifrost-config.json` — Bifrost provider config (Ollama GPU, retries, timeouts) - `channels.py` — channel registry and `deliver()` dispatcher - `router.py` — `Router` class: regex + LLM classification, light-tier reply generation