50 lines
2.6 KiB
Markdown
50 lines
2.6 KiB
Markdown
# AGENTS.md
|
|
|
|
## Run
|
|
```
|
|
python main.py
|
|
```
|
|
Uses `runpy` to exec `bot.py`. No tests, no lint, no CI.
|
|
|
|
## Architecture
|
|
- `bot.py` — Telegram bot (python-telegram-bot), single-worker `asyncio.Queue`, all crawl/parse serialized
|
|
- `agent.py` — pydantic-ai with local Ollama (`granite4.1:8b`), three agents (opportunity/event/workshop), `output_type=str`
|
|
- `database.py` — PocketBase client, collections `events` / `opportunities` / `workshops`
|
|
- `scraper.py` — crawl4ai `AsyncWebCrawler` URL → markdown
|
|
- `prompts.py` — system prompts for both agent types
|
|
- `schemas.py` — **unused at runtime**, per file comment: "File doesn't do anything, its just an outline"
|
|
|
|
## Windows event-loop quirk
|
|
DO NOT remove the dual-event-loop pattern in `bot.py`:
|
|
|
|
1. PTB runs on `WindowsSelectorEventLoopPolicy` (line 357)
|
|
2. Scraper runs in a **separate thread** with `WindowsProactorEventLoopPolicy` (line 25) — required for Playwright subprocesses on Windows
|
|
|
|
The `get_clean_content()` wrapper in `bot.py` runs `_run_scraper_in_thread` via `ThreadPoolExecutor`. Scraper functions must not be called directly.
|
|
|
|
## Agent output format
|
|
Agents use `output_type=str` (not structured output). JSON is parsed manually from `agent.run()` result:
|
|
- LLM returns JSON wrapped in `````json ... ````` markdown backticks
|
|
- `agent.py:56` strips the backtick wrappers before `json.loads()`
|
|
- The markdown wrapper is specified in `prompts.py` — don't change prompt format without updating the strip logic
|
|
|
|
## Date formats
|
|
- Prompts instruct LLM: `DD-MM-YYYY` (or `DD-MM-YYYY (HH:MM)` for events)
|
|
- PocketBase expects: `YYYY-MM-DD HH:MM:SS`
|
|
- `database.py` field mapping: event `date_time` → PocketBase `datetime`, `end_date` → `end_datetime`; workshop same; opportunity `deadline` stays `deadline`
|
|
|
|
## PocketBase collections
|
|
- `events` — fields: `title`, `org`, `datetime`, `end_datetime`, `summary`, `location`, `url`
|
|
- `opportunities` — fields: `title`, `org`, `type`, `deadline`, `summary`, `location`, `url`
|
|
- `workshops` — fields: `title`, `org`, `datetime`, `end_datetime`, `summary`, `location`, `url`
|
|
- Auth uses admin credentials from `.env`
|
|
|
|
## Env vars
|
|
`.env` required: `TG_TOKEN`, `OLLAMA_BASE_URL`, `ALLOWED_USERS`, `POCKETBASE_URL`, `POCKETBASE_ADMIN_EMAIL`, `POCKETBASE_ADMIN_PASSWORD`
|
|
|
|
## Auth
|
|
`ALLOWED_USERS` — comma-separated Telegram user IDs (no brackets). Checked on every command/message handler.
|
|
|
|
## Save flow
|
|
When source is pasted text (not URL), the bot asks for a source URL before saving. `context.user_data` keys: `last_extracted`, `last_source_value`, `last_source_kind`, `last_entry_type`, `awaiting_save_url`, `pending_save_url`, `pending_url_to_process`.
|