TeLLMgramBot 3.13.2__tar.gz → 3.13.4__tar.gz
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.
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/PKG-INFO +14 -15
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/README.md +13 -14
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/TeLLMgramBot.py +11 -11
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/conversation.py +2 -2
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/initialize.py +82 -119
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/message_handlers.py +21 -7
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/tools.py +4 -4
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/PKG-INFO +14 -15
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/setup.py +1 -1
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/LICENSE +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/__init__.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/archive.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/database.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/models.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/__init__.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/anthropic_provider.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/base.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/factory.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/providers/openai_provider.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/utils.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot/web_utils.py +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/requires.txt +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/TeLLMgramBot.egg-info/top_level.txt +0 -0
- {tellmgrambot-3.13.2 → tellmgrambot-3.13.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TeLLMgramBot
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.4
|
|
4
4
|
Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
|
|
5
5
|
Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
|
|
6
6
|
Author: Digital Heresy
|
|
@@ -75,18 +75,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
|
|
|
75
75
|
## Directories
|
|
76
76
|
TeLLMgramBot creates the following directories:
|
|
77
77
|
|
|
78
|
-
- **`configs`** - Bot configuration and model parameters
|
|
79
|
-
- `config.yaml` -
|
|
78
|
+
- **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
|
|
79
|
+
- `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
|
|
80
80
|
- `models.yaml` - Token limits for each LLM model (pre-populated on first run)
|
|
81
|
-
- **`prompts`** - Bot personas
|
|
82
|
-
- `test_personality.prmpt` -
|
|
83
|
-
- `url_analysis.prmpt` - URL summarization prompt template
|
|
81
|
+
- **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
|
|
82
|
+
- `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
|
|
84
83
|
- A system appendix is automatically appended to every persona at runtime, teaching the LLM about cross-chat memory and search behavior. User messages include speaker annotations with chat context and timestamps so the LLM always knows who is speaking, in which chat, and when.
|
|
85
|
-
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `
|
|
86
|
-
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `
|
|
84
|
+
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
|
|
85
|
+
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
|
|
87
86
|
- Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
|
|
88
87
|
- Pass `-v` or `--verbose` on startup for DEBUG-level logging.
|
|
89
|
-
- **`data`** - SQLite database (default `conversations.db`, customizable via `
|
|
88
|
+
- **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
|
|
90
89
|
- Users manage their data via `/forget` and `/private` commands.
|
|
91
90
|
|
|
92
91
|
### Environment Variables for Paths
|
|
@@ -102,13 +101,13 @@ Override default directory locations by setting these environment variables (use
|
|
|
102
101
|
If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
|
|
103
102
|
|
|
104
103
|
## API Keys
|
|
105
|
-
TeLLMgramBot supports four API keys,
|
|
104
|
+
TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
|
|
106
105
|
|
|
107
|
-
| Key | Env Var | File | When required |
|
|
108
|
-
|
|
106
|
+
| Key | Env Var | File/Config | When required |
|
|
107
|
+
|-----|---------|-------------|---------------|
|
|
109
108
|
| [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
|
|
110
109
|
| [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
|
|
111
|
-
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `
|
|
110
|
+
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
|
|
112
111
|
| [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
|
|
113
112
|
|
|
114
113
|
Missing provider keys (OpenAI or Anthropic) disable chat and URL analysis but allow the bot to start. Missing VirusTotal disables URL analysis. Telegram key is required - the bot will not start without it.
|
|
@@ -151,9 +150,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
|
|
|
151
150
|
- `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
|
|
152
151
|
- `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
|
|
153
152
|
- `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
|
|
153
|
+
- `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
|
|
154
154
|
- `bot_nickname` / `bot_initials`: Names the bot responds to in groups
|
|
155
|
-
- `
|
|
156
|
-
- `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
|
|
155
|
+
- `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
|
|
157
156
|
- `token_limit`: Max tokens (optional; defaults to model's maximum)
|
|
158
157
|
- `search_limit`: Max search results (optional; defaults to 30)
|
|
159
158
|
- `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
|
|
@@ -43,18 +43,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
|
|
|
43
43
|
## Directories
|
|
44
44
|
TeLLMgramBot creates the following directories:
|
|
45
45
|
|
|
46
|
-
- **`configs`** - Bot configuration and model parameters
|
|
47
|
-
- `config.yaml` -
|
|
46
|
+
- **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
|
|
47
|
+
- `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
|
|
48
48
|
- `models.yaml` - Token limits for each LLM model (pre-populated on first run)
|
|
49
|
-
- **`prompts`** - Bot personas
|
|
50
|
-
- `test_personality.prmpt` -
|
|
51
|
-
- `url_analysis.prmpt` - URL summarization prompt template
|
|
49
|
+
- **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
|
|
50
|
+
- `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
|
|
52
51
|
- A system appendix is automatically appended to every persona at runtime, teaching the LLM about cross-chat memory and search behavior. User messages include speaker annotations with chat context and timestamps so the LLM always knows who is speaking, in which chat, and when.
|
|
53
|
-
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `
|
|
54
|
-
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `
|
|
52
|
+
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
|
|
53
|
+
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
|
|
55
54
|
- Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
|
|
56
55
|
- Pass `-v` or `--verbose` on startup for DEBUG-level logging.
|
|
57
|
-
- **`data`** - SQLite database (default `conversations.db`, customizable via `
|
|
56
|
+
- **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
|
|
58
57
|
- Users manage their data via `/forget` and `/private` commands.
|
|
59
58
|
|
|
60
59
|
### Environment Variables for Paths
|
|
@@ -70,13 +69,13 @@ Override default directory locations by setting these environment variables (use
|
|
|
70
69
|
If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
|
|
71
70
|
|
|
72
71
|
## API Keys
|
|
73
|
-
TeLLMgramBot supports four API keys,
|
|
72
|
+
TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
|
|
74
73
|
|
|
75
|
-
| Key | Env Var | File | When required |
|
|
76
|
-
|
|
74
|
+
| Key | Env Var | File/Config | When required |
|
|
75
|
+
|-----|---------|-------------|---------------|
|
|
77
76
|
| [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
|
|
78
77
|
| [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
|
|
79
|
-
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `
|
|
78
|
+
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
|
|
80
79
|
| [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
|
|
81
80
|
|
|
82
81
|
Missing provider keys (OpenAI or Anthropic) disable chat and URL analysis but allow the bot to start. Missing VirusTotal disables URL analysis. Telegram key is required - the bot will not start without it.
|
|
@@ -119,9 +118,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
|
|
|
119
118
|
- `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
|
|
120
119
|
- `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
|
|
121
120
|
- `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
|
|
121
|
+
- `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
|
|
122
122
|
- `bot_nickname` / `bot_initials`: Names the bot responds to in groups
|
|
123
|
-
- `
|
|
124
|
-
- `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
|
|
123
|
+
- `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
|
|
125
124
|
- `token_limit`: Max tokens (optional; defaults to model's maximum)
|
|
126
125
|
- `search_limit`: Max search results (optional; defaults to 30)
|
|
127
126
|
- `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
|
|
@@ -103,7 +103,7 @@ class TelegramBot:
|
|
|
103
103
|
self.telegram['username'] = bot.username
|
|
104
104
|
self.telegram['first_name'] = bot.first_name
|
|
105
105
|
self.telegram['last_name'] = bot.last_name
|
|
106
|
-
bind_log_identity(bot.username, self.
|
|
106
|
+
bind_log_identity(bot.username, self._instance_name)
|
|
107
107
|
except Exception as e:
|
|
108
108
|
# Fail fast if the bot's identity cannot be fetched to avoid
|
|
109
109
|
# continuing with an uninitialized self.telegram['username'].
|
|
@@ -183,7 +183,7 @@ class TelegramBot:
|
|
|
183
183
|
List all registered tools available to this bot instance (admin-only, private chat only).
|
|
184
184
|
|
|
185
185
|
Shows name and description for every tool the bot exposes, including the built-in
|
|
186
|
-
search_messages tool and any webhook tools defined in config.
|
|
186
|
+
search_messages tool and any webhook tools defined in bot config. Restricted to
|
|
187
187
|
bot_owner in a private chat; any other caller receives a generic denial.
|
|
188
188
|
|
|
189
189
|
Args:
|
|
@@ -506,7 +506,7 @@ class TelegramBot:
|
|
|
506
506
|
reply = _MSG_PROCESS_ERROR
|
|
507
507
|
if url_match and self.key_status.url_analysis_enabled:
|
|
508
508
|
await msg.reply_text("Sure, give me a moment to look at that URL...")
|
|
509
|
-
reply = await handle_url_ask(text, self.llm['url_model'])
|
|
509
|
+
reply = await handle_url_ask(text, self.llm['url_model'], conv.system_content)
|
|
510
510
|
elif self._online and self.key_status.chat_enabled:
|
|
511
511
|
# This is the transition point between quick Telegram replies and the LLM.
|
|
512
512
|
tools = self._build_tool_list(chat_type, username)
|
|
@@ -964,7 +964,7 @@ class TelegramBot:
|
|
|
964
964
|
archive_days = INIT_BOT_CONFIG['archive_days'],
|
|
965
965
|
persona_prompt = INIT_BOT_CONFIG['persona_prompt'],
|
|
966
966
|
key_status: ApiKeyStatus | None = None,
|
|
967
|
-
|
|
967
|
+
instance_name: str | None = None,
|
|
968
968
|
webhook_schemas: list | None = None,
|
|
969
969
|
webhook_defs: dict | None = None,
|
|
970
970
|
):
|
|
@@ -983,8 +983,8 @@ class TelegramBot:
|
|
|
983
983
|
persona_temp: LLM temperature (0.0-2.0). If None, defaults to 1.0.
|
|
984
984
|
persona_prompt: System prompt defining the bot's behavior and personality.
|
|
985
985
|
key_status: ApiKeyStatus object indicating available features. If None, calls init_structure().
|
|
986
|
-
|
|
987
|
-
|
|
986
|
+
instance_name: Optional label for console prefix and log file name (from bot config `instance_name`).
|
|
987
|
+
Defaults to the bot's Telegram username when not set.
|
|
988
988
|
archive_days: Days before messages are eligible for Tier 1 archival (default: 60).
|
|
989
989
|
Must be an integer >= 1; invalid values log a warning and fall back to 60.
|
|
990
990
|
Tier 2 compression triggers at archive_days * 2.
|
|
@@ -1001,7 +1001,7 @@ class TelegramBot:
|
|
|
1001
1001
|
"""
|
|
1002
1002
|
# Starting to initialize, not online yet
|
|
1003
1003
|
self._online = False
|
|
1004
|
-
self.
|
|
1004
|
+
self._instance_name = instance_name
|
|
1005
1005
|
|
|
1006
1006
|
# Bootstrap structure and logging; init_logging() is a no-op if init_structure already ran it.
|
|
1007
1007
|
self.key_status = key_status if key_status is not None else init_structure()[0]
|
|
@@ -1104,13 +1104,13 @@ class TelegramBot:
|
|
|
1104
1104
|
Side Effects:
|
|
1105
1105
|
- Calls init_structure() which creates directories, config/prompt files, and checks API keys.
|
|
1106
1106
|
- Calls discover_mcp_tools() if any 'mcp_server:' entries are in config (gracefully degrades if called from async context).
|
|
1107
|
-
-
|
|
1108
|
-
- Log identity/file label is taken from config
|
|
1107
|
+
- May log warnings (for missing config values, empty prompt, or skipped MCP discovery), but does not print a startup API key status summary.
|
|
1108
|
+
- Log identity/file label is taken from bot config `instance_name` when set; otherwise defaults to the bot's Telegram username once _tele_info() resolves.
|
|
1109
1109
|
"""
|
|
1110
1110
|
# Bootstrap directories, logging, config, prompt (with appendix), and API keys in one call.
|
|
1111
1111
|
key_status, config, prompt = init_structure(config_file, prompt_file)
|
|
1112
1112
|
|
|
1113
|
-
# Build the webhook tool registry from the optional 'tools:' block in config.
|
|
1113
|
+
# Build the webhook tool registry from the optional 'tools:' block in bot config.
|
|
1114
1114
|
allow_local = config['allow_local_webhooks'] or False
|
|
1115
1115
|
webhook_schemas, webhook_defs = build_tool_registry(config.get('tools') or [], allow_local)
|
|
1116
1116
|
|
|
@@ -1145,7 +1145,7 @@ class TelegramBot:
|
|
|
1145
1145
|
archive_days = config['archive_days'],
|
|
1146
1146
|
persona_prompt = prompt,
|
|
1147
1147
|
key_status = key_status,
|
|
1148
|
-
|
|
1148
|
+
instance_name = config['instance_name'],
|
|
1149
1149
|
webhook_schemas = webhook_schemas,
|
|
1150
1150
|
webhook_defs = webhook_defs,
|
|
1151
1151
|
)
|
|
@@ -111,9 +111,9 @@ class Conversation:
|
|
|
111
111
|
init and on every incoming message so long sessions always carry an accurate
|
|
112
112
|
UTC timestamp in format 'YYYY-MM-DD HH:MM UTC'.
|
|
113
113
|
"""
|
|
114
|
-
dt_line = f'\
|
|
114
|
+
dt_line = f'\n- Current date and time: {format_now(None)}'
|
|
115
115
|
new_content, n = re.subn(
|
|
116
|
-
r'\
|
|
116
|
+
r'\n-?\s*Current date and time:.*',
|
|
117
117
|
dt_line,
|
|
118
118
|
self.system_content,
|
|
119
119
|
)
|
|
@@ -96,14 +96,16 @@ class ApiKeyStatus:
|
|
|
96
96
|
disabled_reasons: dict = field(default_factory=dict)
|
|
97
97
|
|
|
98
98
|
|
|
99
|
+
_TELEGRAM_KEY_RE = re.compile(r'^\d+:[A-Za-z0-9_-]+$')
|
|
100
|
+
|
|
99
101
|
INIT_BOT_CONFIG = {
|
|
100
102
|
'bot_owner': '<YOUR USERNAME>',
|
|
101
103
|
'bot_nickname': 'Testy',
|
|
102
104
|
'bot_initials': 'TB',
|
|
103
105
|
'chat_model': 'gpt-5-mini',
|
|
104
106
|
'url_model': 'gpt-5',
|
|
105
|
-
'
|
|
106
|
-
'
|
|
107
|
+
'telegram_api_key': '<YOUR TELEGRAM API KEY HERE>',
|
|
108
|
+
'instance_name': None,
|
|
107
109
|
'token_limit': None,
|
|
108
110
|
'search_limit': None,
|
|
109
111
|
'persona_temp': None,
|
|
@@ -113,8 +115,7 @@ INIT_BOT_CONFIG = {
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
INIT_BOT_CONFIG_COMMENTS = {
|
|
116
|
-
'
|
|
117
|
-
'log_name': '# Optional, label for console prefix and log file name. Defaults to the bot\'s Telegram username',
|
|
118
|
+
'instance_name': '# Optional, sets log label and DB filename (e.g. MyBot -> MyBot.db). If omitted, log label defaults to the bot\'s Telegram username and DB filename remains conversations.db',
|
|
118
119
|
'token_limit': '# Optional, overrides the chat_model\'s default max token limit',
|
|
119
120
|
'search_limit': '# Optional, max results returned by message search (default: 30)',
|
|
120
121
|
'persona_temp': '# Optional, LLM temperature 0.0-2.0 (default: model\'s default)',
|
|
@@ -125,12 +126,13 @@ INIT_BOT_CONFIG_COMMENTS = {
|
|
|
125
126
|
# Append the framework-owned system appendix to the persona prompt.
|
|
126
127
|
# Teaches an LLM how cross-chat memory works without requiring persona authors to include it.
|
|
127
128
|
_SYSTEM_APPENDIX = (
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
129
|
+
"## System\n"
|
|
130
|
+
"- You have access to past messages across chats and a search tool - use them to inform your responses, but never quote or volunteer content from other chats unprompted, especially in group settings.\n"
|
|
131
|
+
"- Always invoke the search tool before concluding a message does not exist.\n"
|
|
132
|
+
"- Messages marked private are never shared between chats.\n"
|
|
133
|
+
"- Never reveal or repeat Telegram user IDs, chat IDs, or any other internal numeric identifiers in your responses.\n"
|
|
134
|
+
"- When your context includes a '[Replying to Name, ...]' prefix indicating you were triggered via a reply, acknowledge the original author by name in your response.\n"
|
|
135
|
+
"- Current date and time: (not yet known)\n"
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
|
|
@@ -165,24 +167,24 @@ def init_logging():
|
|
|
165
167
|
logging.getLogger('httpcore').setLevel(logging.WARNING)
|
|
166
168
|
|
|
167
169
|
|
|
168
|
-
def bind_log_identity(username: str,
|
|
170
|
+
def bind_log_identity(username: str, instance_name: Optional[str] = None):
|
|
169
171
|
"""
|
|
170
172
|
Bind the bot's identity to the logging system.
|
|
171
173
|
|
|
172
|
-
Updates the console handler formatter to prefix every line with [
|
|
173
|
-
the per-instance log file named {
|
|
174
|
+
Updates the console handler formatter to prefix every line with [instance_name] and opens
|
|
175
|
+
the per-instance log file named {instance_name}_{timestamp}.log.
|
|
174
176
|
|
|
175
177
|
Must be called after the bot's Telegram username is resolved by _tele_info().
|
|
176
178
|
|
|
177
179
|
Args:
|
|
178
180
|
username: The bot's Telegram username (without @); used as the identity label when
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
instance_name is not set.
|
|
182
|
+
instance_name: Optional override from bot config. When set, used for both the console
|
|
183
|
+
prefix and log file name instead of the Telegram username -- useful for
|
|
184
|
+
multi-platform deployments where the same bot runs on Telegram and Discord.
|
|
183
185
|
"""
|
|
184
|
-
raw_label =
|
|
185
|
-
# Sanitize: strip path separators so a misconfigured
|
|
186
|
+
raw_label = instance_name or username
|
|
187
|
+
# Sanitize: strip path separators so a misconfigured instance_name cannot escape TELLMGRAMBOT_LOGS_PATH
|
|
186
188
|
label = os.path.basename(raw_label).strip()
|
|
187
189
|
if not label:
|
|
188
190
|
label = username
|
|
@@ -244,7 +246,7 @@ def _requires_provider(config: dict) -> dict:
|
|
|
244
246
|
all other non-empty models require OpenAI.
|
|
245
247
|
|
|
246
248
|
Args:
|
|
247
|
-
config: Parsed bot configuration dict (from init_bot_config or read_yaml on config
|
|
249
|
+
config: Parsed bot configuration dict (from init_bot_config or read_yaml on bot config).
|
|
248
250
|
|
|
249
251
|
Returns:
|
|
250
252
|
Dict with keys 'openai' and 'anthropic', each a bool indicating if that provider key is needed.
|
|
@@ -266,66 +268,76 @@ def _model_provider(model: str) -> str | None:
|
|
|
266
268
|
return 'anthropic' if model.startswith('claude-') else 'openai'
|
|
267
269
|
|
|
268
270
|
|
|
269
|
-
def
|
|
271
|
+
def _load_telegram_key(config: dict):
|
|
270
272
|
"""
|
|
271
|
-
Load
|
|
273
|
+
Load and validate the Telegram API key into TELLMGRAMBOT_TELEGRAM_API_KEY.
|
|
274
|
+
Lookup order: (1) TELLMGRAMBOT_TELEGRAM_API_KEY env var, (2) bot config
|
|
275
|
+
telegram_api_key. Exits on placeholder, missing, or malformed key.
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
Args:
|
|
278
|
+
config: Parsed bot configuration dict from init_bot_config().
|
|
279
|
+
"""
|
|
280
|
+
env_var = 'TELLMGRAMBOT_TELEGRAM_API_KEY'
|
|
281
|
+
if not os.environ.get(env_var):
|
|
282
|
+
config_val = (config.get('telegram_api_key') or '').strip()
|
|
283
|
+
is_placeholder = config_val.startswith('<') and config_val.endswith('>')
|
|
284
|
+
if config_val and not is_placeholder:
|
|
285
|
+
os.environ[env_var] = config_val
|
|
286
|
+
elif is_placeholder:
|
|
287
|
+
sys.exit("telegram_api_key in bot config is still a placeholder. Replace it with your Telegram bot API key. Exiting...")
|
|
288
|
+
else:
|
|
289
|
+
sys.exit(
|
|
290
|
+
"Telegram API key is required. Set telegram_api_key in bot config "
|
|
291
|
+
"or TELLMGRAMBOT_TELEGRAM_API_KEY env var. Exiting..."
|
|
292
|
+
)
|
|
293
|
+
if not _TELEGRAM_KEY_RE.match(os.environ.get(env_var, '')):
|
|
294
|
+
sys.exit(
|
|
295
|
+
"Telegram API key is not in valid format '<digits>:<letters/digits/_/->' "
|
|
296
|
+
"(e.g. 123456789:ABCdef...) - exiting..."
|
|
297
|
+
)
|
|
276
298
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
299
|
+
|
|
300
|
+
def init_keys(config: dict) -> ApiKeyStatus:
|
|
301
|
+
"""
|
|
302
|
+
Load API keys and return an ApiKeyStatus indicating which features are available.
|
|
303
|
+
|
|
304
|
+
Telegram key is loaded and validated first via _load_telegram_key(). All other
|
|
305
|
+
keys (OpenAI, Anthropic, VirusTotal) are optional: missing keys disable the
|
|
306
|
+
associated features rather than exiting. These keys are loaded from env var or
|
|
307
|
+
key file (TELLMGRAMBOT_KEYS_PATH or base execution path). Placeholder files are
|
|
308
|
+
created for any missing keys.
|
|
281
309
|
|
|
282
310
|
Args:
|
|
283
|
-
config:
|
|
284
|
-
TELLMGRAMBOT_CONFIGS_PATH/config.yaml (or execution_dir/config.yaml).
|
|
311
|
+
config: Parsed bot configuration dict from init_bot_config().
|
|
285
312
|
|
|
286
313
|
Returns:
|
|
287
314
|
ApiKeyStatus with chat_enabled and url_analysis_enabled flags set based on which keys are available.
|
|
288
315
|
"""
|
|
289
|
-
|
|
290
|
-
# Failures here are non-fatal: config = {} means no models configured.
|
|
291
|
-
if config is None:
|
|
292
|
-
try:
|
|
293
|
-
config_path = os.path.join(os.environ.get('TELLMGRAMBOT_CONFIGS_PATH', execution_dir()), 'config.yaml')
|
|
294
|
-
config = read_yaml(config_path) or {}
|
|
295
|
-
except Exception:
|
|
296
|
-
config = {}
|
|
316
|
+
_load_telegram_key(config)
|
|
297
317
|
|
|
298
318
|
# Determine which provider keys are needed and which feature each model uses.
|
|
299
319
|
provider_needs = _requires_provider(config)
|
|
300
320
|
chat_provider = _model_provider(config.get('chat_model', ''))
|
|
301
321
|
url_provider = _model_provider(config.get('url_model', ''))
|
|
302
322
|
|
|
303
|
-
#
|
|
323
|
+
# Only load the required provider key(s) + VirusTotal.
|
|
304
324
|
provider_keys = {
|
|
305
325
|
'openai': 'https://platform.openai.com/api-keys',
|
|
306
326
|
'anthropic': 'https://docs.anthropic.com/en/api/getting-started',
|
|
307
327
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
'virustotal': 'https://developers.virustotal.com/reference/overview'
|
|
311
|
-
}
|
|
312
|
-
keys = {k: v for k, v in provider_keys.items() if provider_needs.get(k)} | always_keys
|
|
328
|
+
optional_keys = {k: v for k, v in provider_keys.items() if provider_needs.get(k)}
|
|
329
|
+
optional_keys['virustotal'] = 'https://developers.virustotal.com/reference/overview'
|
|
313
330
|
|
|
314
|
-
available = set() #
|
|
315
|
-
for key, url in
|
|
331
|
+
available = set(['telegram']) # telegram already validated above
|
|
332
|
+
for key, url in optional_keys.items():
|
|
316
333
|
env_var = f"TELLMGRAMBOT_{key.upper()}_API_KEY"
|
|
317
334
|
|
|
318
|
-
# If the key isn't already in the environment, try loading it from a key file.
|
|
319
335
|
if not os.environ.get(env_var):
|
|
320
336
|
path = os.path.join(os.environ.get('TELLMGRAMBOT_KEYS_PATH', execution_dir()), f"{key}.key")
|
|
321
337
|
if not os.path.exists(path):
|
|
322
|
-
# Key file missing: create a placeholder and warn (or exit for Telegram).
|
|
323
338
|
generate_file_path(os.path.dirname(path), os.path.basename(path), "API key",
|
|
324
339
|
f"YOUR {key.upper()} API KEY HERE - {url}\n"
|
|
325
340
|
)
|
|
326
|
-
if key == 'telegram':
|
|
327
|
-
sys.exit("Telegram API key is required to run TeLLMgramBot! Exiting...")
|
|
328
|
-
# Build a human-readable list of which features this key covers.
|
|
329
341
|
which = [f"chat_model ({config.get('chat_model', '?')})" if chat_provider == key else None,
|
|
330
342
|
f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
|
|
331
343
|
"VirusTotal integration" if key == 'virustotal' else None]
|
|
@@ -333,14 +345,10 @@ def init_keys(config: Optional[dict] = None) -> ApiKeyStatus:
|
|
|
333
345
|
logger.warning(f"{key}.key not found - {reason} will be disabled.")
|
|
334
346
|
continue
|
|
335
347
|
else:
|
|
336
|
-
# Key file exists: load it into the environment for this process.
|
|
337
348
|
os.environ[env_var] = read_text(path)
|
|
338
349
|
logger.info(f"Loaded {env_var} secret from: {path}")
|
|
339
350
|
|
|
340
|
-
# Reject blank or whitespace-only values (common copy-paste mistake).
|
|
341
351
|
if not os.environ.get(env_var) or any(c.isspace() for c in os.environ.get(env_var)):
|
|
342
|
-
if key == 'telegram':
|
|
343
|
-
sys.exit("Telegram API key is blank or invalid! Exiting...")
|
|
344
352
|
which = [f"chat_model ({config.get('chat_model', '?')})" if chat_provider == key else None,
|
|
345
353
|
f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
|
|
346
354
|
"VirusTotal integration" if key == 'virustotal' else None]
|
|
@@ -374,26 +382,9 @@ def init_keys(config: Optional[dict] = None) -> ApiKeyStatus:
|
|
|
374
382
|
reasons.append("missing VirusTotal API key")
|
|
375
383
|
key_status.disabled_reasons['url_analysis'] = ", ".join(reasons)
|
|
376
384
|
|
|
377
|
-
print_api_key_status(key_status)
|
|
378
385
|
return key_status
|
|
379
386
|
|
|
380
387
|
|
|
381
|
-
def print_api_key_status(key_status: ApiKeyStatus):
|
|
382
|
-
"""Print a startup summary of which API keys are available and which features they enable."""
|
|
383
|
-
print("--- TeLLMgramBot API Key Status ---")
|
|
384
|
-
features = [
|
|
385
|
-
('chat', 'Chat (LLM)', key_status.chat_enabled),
|
|
386
|
-
('url_analysis', 'URL Analysis', key_status.url_analysis_enabled),
|
|
387
|
-
]
|
|
388
|
-
for key, label, enabled in features:
|
|
389
|
-
if enabled:
|
|
390
|
-
print(f" {label:<12} : ENABLED")
|
|
391
|
-
else:
|
|
392
|
-
reason = key_status.disabled_reasons.get(key, 'unavailable')
|
|
393
|
-
print(f" {label:<12} : DISABLED ({reason})")
|
|
394
|
-
print("-----------------------------------")
|
|
395
|
-
|
|
396
|
-
|
|
397
388
|
def init_bot_config(file: str = 'config.yaml') -> dict:
|
|
398
389
|
"""
|
|
399
390
|
Returns contents of a bot configuration file (default 'config.yaml' if created).
|
|
@@ -412,9 +403,9 @@ def init_bot_config(file: str = 'config.yaml') -> dict:
|
|
|
412
403
|
env_var = "TELLMGRAMBOT_CONFIGS_PATH"
|
|
413
404
|
try:
|
|
414
405
|
text = ''.join(
|
|
415
|
-
'%s
|
|
416
|
-
parameter
|
|
417
|
-
value if value else INIT_BOT_CONFIG_COMMENTS.get(parameter, '# Optional')
|
|
406
|
+
'%s: %s\n' % (
|
|
407
|
+
parameter,
|
|
408
|
+
(f"'{value}'" if isinstance(value, str) else value) if value is not None else INIT_BOT_CONFIG_COMMENTS.get(parameter, '# Optional')
|
|
418
409
|
)
|
|
419
410
|
for parameter, value in INIT_BOT_CONFIG.items()
|
|
420
411
|
if parameter != 'persona_prompt'
|
|
@@ -497,33 +488,6 @@ def init_bot_prompt(file: str = 'test_personality.prmpt') -> str:
|
|
|
497
488
|
sys.exit(f"{type(e).__name__} on {env_var} file '{file}': {e}\nExiting...")
|
|
498
489
|
|
|
499
490
|
|
|
500
|
-
def init_url_prompt(file: str = 'url_analysis.prmpt') -> str:
|
|
501
|
-
"""
|
|
502
|
-
Ensure prompt file is created that defines how the bot will analyze URLs via square brackets [].
|
|
503
|
-
Must specify the environment variable 'TELLMGRAMBOT_PROMPTS_PATH'.
|
|
504
|
-
"""
|
|
505
|
-
env_var = "TELLMGRAMBOT_PROMPTS_PATH"
|
|
506
|
-
try:
|
|
507
|
-
return read_text(
|
|
508
|
-
generate_file_path(os.environ[env_var], file, "URL analysis prompt",
|
|
509
|
-
"The user has provided a URL to perform some level of analysis. You will infer "
|
|
510
|
-
"the nature of the analysis from the user's query.\n\n"
|
|
511
|
-
"The contents of the URL mentioned have already been harvested and cleansed. "
|
|
512
|
-
"Note the URL contents will likely have sections of text that are less relevant "
|
|
513
|
-
"to the user's question (headers, footers, menus, ads, etc.). You will need to "
|
|
514
|
-
"ignore those sections of text and focus on the main content of the page.\n\n"
|
|
515
|
-
"The contents of the URL are shown below:\n"
|
|
516
|
-
"BEGIN URL CONTENTS\n"
|
|
517
|
-
"{url_content}\n"
|
|
518
|
-
"END URL CONTENTS\n"
|
|
519
|
-
)
|
|
520
|
-
)
|
|
521
|
-
except KeyError:
|
|
522
|
-
sys.exit(f"{env_var} must be defined to create file '{file}'! Exiting...")
|
|
523
|
-
except Exception as e:
|
|
524
|
-
sys.exit(f"{type(e).__name__} on {env_var} file '{file}': {e}\nExiting...")
|
|
525
|
-
|
|
526
|
-
|
|
527
491
|
def init_structure(
|
|
528
492
|
config_file: str = 'config.yaml',
|
|
529
493
|
prompt_file: str = 'test_personality.prmpt',
|
|
@@ -532,15 +496,15 @@ def init_structure(
|
|
|
532
496
|
Performs the whole TeLLMgramBot first-run setup including:
|
|
533
497
|
- Directories for configurations, prompts, and conversation/error logs.
|
|
534
498
|
- Logging configuration (console handler; file handler bound later by bind_log_identity()).
|
|
535
|
-
- Database naming and schema initialization from config (conversations.db or custom
|
|
536
|
-
- Provider-conditional API keys (OpenAI/Anthropic determined by config
|
|
537
|
-
- Configuration files (config
|
|
538
|
-
- System prompt
|
|
499
|
+
- Database naming and schema initialization from config (conversations.db or custom instance_name).
|
|
500
|
+
- Provider-conditional API keys (OpenAI/Anthropic determined by bot config model prefixes).
|
|
501
|
+
- Configuration files (bot config, models.yaml) with inline parameter descriptions.
|
|
502
|
+
- System prompt file (test_personality.prmpt).
|
|
539
503
|
|
|
540
|
-
Provider key requirements are inferred from config
|
|
504
|
+
Provider key requirements are inferred from bot config model prefixes (claude-* -> Anthropic,
|
|
541
505
|
gpt-* -> OpenAI, etc.) to streamline setup for single-provider deployments.
|
|
542
506
|
|
|
543
|
-
Database naming: After init_bot_config() runs, if
|
|
507
|
+
Database naming: After init_bot_config() runs, if instance_name is configured in bot config,
|
|
544
508
|
calls set_db_filename() and performs a one-time rename migration of conversations.db to
|
|
545
509
|
the custom filename (only if the target does not yet exist). This ordering ensures the DB
|
|
546
510
|
filename is settled before schema init.
|
|
@@ -563,19 +527,19 @@ def init_structure(
|
|
|
563
527
|
init_models_config()
|
|
564
528
|
|
|
565
529
|
# Configure DB filename and run schema init now that config is available.
|
|
566
|
-
# Must happen after init_bot_config() so
|
|
567
|
-
|
|
568
|
-
if
|
|
530
|
+
# Must happen after init_bot_config() so instance_name is known before init_db() runs.
|
|
531
|
+
instance_name = config.get('instance_name')
|
|
532
|
+
if instance_name:
|
|
569
533
|
# Sanitize to a plain filename - reject path separators and absolute paths so a
|
|
570
|
-
# misconfigured
|
|
571
|
-
|
|
572
|
-
if
|
|
534
|
+
# misconfigured instance_name cannot escape TELLMGRAMBOT_DATA_PATH.
|
|
535
|
+
instance_name_safe = os.path.basename(instance_name).strip()
|
|
536
|
+
if instance_name_safe != instance_name.strip() or not instance_name_safe:
|
|
573
537
|
logger.warning(
|
|
574
|
-
f"
|
|
538
|
+
f"instance_name '{instance_name}' contains path components; ignoring and using conversations.db"
|
|
575
539
|
)
|
|
576
|
-
|
|
540
|
+
instance_name = None
|
|
577
541
|
else:
|
|
578
|
-
set_db_filename(
|
|
542
|
+
set_db_filename(instance_name_safe)
|
|
579
543
|
# One-time migration: rename conversations.db to the new name if the target
|
|
580
544
|
# doesn't exist yet. Assumes a single-bot deployment owns conversations.db.
|
|
581
545
|
data_dir = os.environ.get('TELLMGRAMBOT_DATA_PATH', os.path.join(execution_dir(), 'data'))
|
|
@@ -608,7 +572,6 @@ def init_structure(
|
|
|
608
572
|
prompt = INIT_BOT_CONFIG.get('persona_prompt', '')
|
|
609
573
|
logger.warning(f"File '{prompt_file}' is empty, using default persona prompt.")
|
|
610
574
|
prompt = prompt.rstrip() + "\n\n" + _SYSTEM_APPENDIX
|
|
611
|
-
init_url_prompt()
|
|
612
575
|
|
|
613
576
|
# Load API keys before running archival so the provider can authenticate.
|
|
614
577
|
key_status = init_keys(config)
|
|
@@ -3,7 +3,6 @@ import re
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
import validators
|
|
5
5
|
|
|
6
|
-
from .initialize import init_url_prompt
|
|
7
6
|
from .utils import log_error
|
|
8
7
|
from .models import TokenLimits
|
|
9
8
|
from .web_utils import (
|
|
@@ -15,6 +14,20 @@ from .web_utils import (
|
|
|
15
14
|
)
|
|
16
15
|
from .providers.factory import get_provider
|
|
17
16
|
|
|
17
|
+
_URL_ANALYSIS_TEMPLATE = (
|
|
18
|
+
"## URL Analysis\n"
|
|
19
|
+
"The user has provided a URL to perform some level of analysis. You will infer "
|
|
20
|
+
"the nature of the analysis from the user's query.\n\n"
|
|
21
|
+
"The contents of the URL mentioned have already been harvested and cleansed. "
|
|
22
|
+
"Note the URL contents will likely have sections of text that are less relevant "
|
|
23
|
+
"to the user's question (headers, footers, menus, ads, etc.). You will need to "
|
|
24
|
+
"ignore those sections of text and focus on the main content of the page.\n\n"
|
|
25
|
+
"The contents of the URL are shown below:\n"
|
|
26
|
+
"BEGIN URL CONTENTS\n"
|
|
27
|
+
"{url_content}\n"
|
|
28
|
+
"END URL CONTENTS\n"
|
|
29
|
+
)
|
|
30
|
+
|
|
18
31
|
|
|
19
32
|
def handle_greetings(text: str) -> Optional[str]:
|
|
20
33
|
"""
|
|
@@ -44,18 +57,19 @@ def handle_common_queries(text: str) -> Optional[str]:
|
|
|
44
57
|
return None
|
|
45
58
|
|
|
46
59
|
|
|
47
|
-
async def handle_url_ask(text: str, model: str = 'gpt-4o') -> Optional[str]:
|
|
60
|
+
async def handle_url_ask(text: str, model: str = 'gpt-4o', prompt: str = '') -> Optional[str]:
|
|
48
61
|
"""
|
|
49
62
|
Process URL content in an LLM to provide a summary.
|
|
50
63
|
|
|
51
64
|
Extracts URLs wrapped in square brackets [], validates them, checks for
|
|
52
65
|
safety via VirusTotal, fetches content, and summarizes via an LLM specified
|
|
53
|
-
by model name.
|
|
54
|
-
|
|
66
|
+
by model name. The bot's persona prompt is prepended to the URL analysis
|
|
67
|
+
system message so responses match the bot's personality.
|
|
55
68
|
|
|
56
69
|
Args:
|
|
57
70
|
text: The message text potentially containing a URL in [square brackets].
|
|
58
71
|
model: The LLM model to use for URL summarization (default: 'gpt-4o').
|
|
72
|
+
prompt: Bot persona prompt prepended to the URL analysis system message.
|
|
59
73
|
|
|
60
74
|
Returns:
|
|
61
75
|
A summary string if a URL was found and processed successfully, an error
|
|
@@ -104,9 +118,9 @@ async def handle_url_ask(text: str, model: str = 'gpt-4o') -> Optional[str]:
|
|
|
104
118
|
# Show the last 50 characters of the pruned URL content
|
|
105
119
|
pruned_tail = messages[0]["content"][-50:]
|
|
106
120
|
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
messages[0]["content"] =
|
|
121
|
+
# Build system message: bot persona (if any) + URL analysis template with content
|
|
122
|
+
url_system = _URL_ANALYSIS_TEMPLATE.replace('{url_content}', messages[0]["content"])
|
|
123
|
+
messages[0]["content"] = f"{prompt}\n\n{url_system}" if prompt else url_system
|
|
110
124
|
|
|
111
125
|
# Call the LLM for the response that summarizes URL content
|
|
112
126
|
try:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Webhook and MCP tool registry and executors for TeLLMgramBot.
|
|
3
3
|
|
|
4
|
-
Provides build_tool_registry() for startup parsing of config
|
|
4
|
+
Provides build_tool_registry() for startup parsing of bot config 'tools:' webhook
|
|
5
5
|
entries, discover_mcp_tools() for async MCP server discovery, execute_webhook() for
|
|
6
6
|
dispatching webhook tool calls, and execute_mcp() for MCP tool calls at runtime.
|
|
7
7
|
"""
|
|
@@ -96,7 +96,7 @@ def build_tool_registry(
|
|
|
96
96
|
are skipped with warnings. Duplicate tool names are deduplicated (first wins).
|
|
97
97
|
|
|
98
98
|
Args:
|
|
99
|
-
tools_config: List of raw tool definition dicts from config
|
|
99
|
+
tools_config: List of raw tool definition dicts from bot config 'tools:' key.
|
|
100
100
|
Pass an empty list or None to produce an empty registry.
|
|
101
101
|
allow_local: If True, the executor permits webhook URLs targeting loopback or
|
|
102
102
|
link-local addresses. Mirrors the 'allow_local_webhooks' config key.
|
|
@@ -387,7 +387,7 @@ async def execute_mcp(tool_def: dict, arguments: dict) -> str:
|
|
|
387
387
|
logger.warning(f"MCP '{name}': SSRF block - '{parsed.hostname}' is loopback or link-local")
|
|
388
388
|
return (
|
|
389
389
|
f"[Tool error: '{name}' targets a loopback or link-local address. "
|
|
390
|
-
f"Set allow_local_webhooks: true in config
|
|
390
|
+
f"Set allow_local_webhooks: true in bot config to permit local addresses.]"
|
|
391
391
|
)
|
|
392
392
|
|
|
393
393
|
call_url = f"{server_url}/tools/call"
|
|
@@ -489,7 +489,7 @@ async def execute_webhook(tool_def: dict, arguments: dict) -> str:
|
|
|
489
489
|
logger.warning(f"Webhook '{name}': SSRF block - '{hostname}' is loopback or link-local")
|
|
490
490
|
return (
|
|
491
491
|
f"[Tool error: '{name}' targets a loopback or link-local address. "
|
|
492
|
-
f"Set allow_local_webhooks: true in config
|
|
492
|
+
f"Set allow_local_webhooks: true in bot config to permit local addresses.]"
|
|
493
493
|
)
|
|
494
494
|
|
|
495
495
|
host_part = f"{parsed.hostname}:{parsed.port}" if parsed.port else parsed.hostname
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TeLLMgramBot
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.4
|
|
4
4
|
Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
|
|
5
5
|
Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
|
|
6
6
|
Author: Digital Heresy
|
|
@@ -75,18 +75,17 @@ Simply set `chat_model` (and optionally `url_model`) in your `config.yaml` to an
|
|
|
75
75
|
## Directories
|
|
76
76
|
TeLLMgramBot creates the following directories:
|
|
77
77
|
|
|
78
|
-
- **`configs`** - Bot configuration and model parameters
|
|
79
|
-
- `config.yaml` -
|
|
78
|
+
- **`configs`** - Bot configuration and model parameters (path configurable via `TELLMGRAMBOT_CONFIGS_PATH`)
|
|
79
|
+
- `config.yaml` - Default bot configuration file (filename used throughout this README); can be changed by passing `config_file` to `TelegramBot.set()`
|
|
80
80
|
- `models.yaml` - Token limits for each LLM model (pre-populated on first run)
|
|
81
|
-
- **`prompts`** - Bot personas
|
|
82
|
-
- `test_personality.prmpt` -
|
|
83
|
-
- `url_analysis.prmpt` - URL summarization prompt template
|
|
81
|
+
- **`prompts`** - Bot personas (path configurable via `TELLMGRAMBOT_PROMPTS_PATH`)
|
|
82
|
+
- `test_personality.prmpt` - Default bot persona file (filename used throughout this README); can be changed by passing `prompt_file` to `TelegramBot.set()`
|
|
84
83
|
- A system appendix is automatically appended to every persona at runtime, teaching the LLM about cross-chat memory and search behavior. User messages include speaker annotations with chat context and timestamps so the LLM always knows who is speaking, in which chat, and when.
|
|
85
|
-
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `
|
|
86
|
-
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `
|
|
84
|
+
- **`logs`** - Bot instance logs (one per startup, named after the bot's Telegram username or `instance_name` config, e.g. `my_bot_2026-03-29_10-30-45.log`)
|
|
85
|
+
- Logs include anonymized Telegram IDs for privacy. Console shows INFO-level TeLLMgramBot messages only, prefixed with an `[identity label]` (the bot's Telegram username by default, or `instance_name` when configured).
|
|
87
86
|
- Bot keeps the 10 most recent logs per bot instance, automatically pruning older ones.
|
|
88
87
|
- Pass `-v` or `--verbose` on startup for DEBUG-level logging.
|
|
89
|
-
- **`data`** - SQLite database (default `conversations.db`, customizable via `
|
|
88
|
+
- **`data`** - SQLite database (default `conversations.db`, customizable via `instance_name` config) storing all messages, users, and chats
|
|
90
89
|
- Users manage their data via `/forget` and `/private` commands.
|
|
91
90
|
|
|
92
91
|
### Environment Variables for Paths
|
|
@@ -102,13 +101,13 @@ Override default directory locations by setting these environment variables (use
|
|
|
102
101
|
If unset, all paths default to subdirectories of the execution directory (the directory containing your entry-point script).
|
|
103
102
|
|
|
104
103
|
## API Keys
|
|
105
|
-
TeLLMgramBot supports four API keys,
|
|
104
|
+
TeLLMgramBot supports four API keys. OpenAI, Anthropic, and VirusTotal keys load from environment variables or `.key` files. The Telegram key loads from its env var or the bot config field `telegram_api_key` (no `.key` file):
|
|
106
105
|
|
|
107
|
-
| Key | Env Var | File | When required |
|
|
108
|
-
|
|
106
|
+
| Key | Env Var | File/Config | When required |
|
|
107
|
+
|-----|---------|-------------|---------------|
|
|
109
108
|
| [OpenAI](https://platform.openai.com/api-keys) | `TELLMGRAMBOT_OPENAI_API_KEY` | `openai.key` | For `gpt-*` models |
|
|
110
109
|
| [Anthropic](https://console.anthropic.com/settings/keys) | `TELLMGRAMBOT_ANTHROPIC_API_KEY` | `anthropic.key` | For `claude-*` models |
|
|
111
|
-
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | `
|
|
110
|
+
| [Telegram](https://t.me/BotFather) | `TELLMGRAMBOT_TELEGRAM_API_KEY` | bot config `telegram_api_key` in `config.yaml` | Always required |
|
|
112
111
|
| [VirusTotal](https://www.virustotal.com/gui/my-apikey) | `TELLMGRAMBOT_VIRUSTOTAL_API_KEY` | `virustotal.key` | For URL analysis |
|
|
113
112
|
|
|
114
113
|
Missing provider keys (OpenAI or Anthropic) disable chat and URL analysis but allow the bot to start. Missing VirusTotal disables URL analysis. Telegram key is required - the bot will not start without it.
|
|
@@ -151,9 +150,9 @@ When the bot is triggered in a group and about to respond (not deferring to anot
|
|
|
151
150
|
- `bot_owner`: Telegram username(s) with admin access (required, no `@`). Accepts a single string or a YAML list of usernames.
|
|
152
151
|
- `chat_model`: LLM model for conversation (e.g. `gpt-4o-mini` or `claude-sonnet-4-6`)
|
|
153
152
|
- `url_model`: LLM model for URL analysis (e.g. `gpt-4o` or `claude-haiku-4-5`)
|
|
153
|
+
- `telegram_api_key`: Telegram bot API key (required). Lookup order: (1) `TELLMGRAMBOT_TELEGRAM_API_KEY` env var, (2) `telegram_api_key` in `config.yaml`. Exits on placeholder, missing, or malformed key.
|
|
154
154
|
- `bot_nickname` / `bot_initials`: Names the bot responds to in groups
|
|
155
|
-
- `
|
|
156
|
-
- `log_name`: Optional label used for the console prefix and log filename (e.g. `MyBot` produces `[MyBot] INFO: ...` on the console and `MyBot_{timestamp}.log`); omit to use the bot's Telegram username. Useful for multi-platform deployments sharing the same config.
|
|
155
|
+
- `instance_name`: Optional label for console prefix, log filename, and database name (e.g. `MyBot` produces `[MyBot] INFO: ...` on console, `MyBot_{timestamp}.log` logs, and `MyBot.db` database); omit to use bot's Telegram username for logging and `conversations.db` for database. Use distinct names when running multiple bot instances in the same directory.
|
|
157
156
|
- `token_limit`: Max tokens (optional; defaults to model's maximum)
|
|
158
157
|
- `search_limit`: Max search results (optional; defaults to 30)
|
|
159
158
|
- `archive_days`: Days before messages are eligible for archival (optional; default 60, minimum 1). Older messages are distilled into daily summaries, then progressively compressed into monthly digests. Once archived their respective raw messages do not return to the LLM context any more, only when searching messages.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|