TeLLMgramBot 3.2.0__tar.gz → 3.2.2__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.
Files changed (24) hide show
  1. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/PKG-INFO +1 -1
  2. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/TeLLMgramBot.py +26 -19
  3. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/conversation.py +43 -13
  4. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/database.py +33 -10
  5. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/initialize.py +5 -5
  6. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/factory.py +2 -2
  7. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/PKG-INFO +1 -1
  8. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/setup.py +1 -1
  9. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/LICENSE +0 -0
  10. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/README.md +0 -0
  11. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/__init__.py +0 -0
  12. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/message_handlers.py +0 -0
  13. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/models.py +0 -0
  14. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/__init__.py +0 -0
  15. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/anthropic_provider.py +0 -0
  16. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/base.py +0 -0
  17. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/providers/openai_provider.py +0 -0
  18. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/utils.py +0 -0
  19. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot/web_utils.py +0 -0
  20. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/SOURCES.txt +0 -0
  21. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/dependency_links.txt +0 -0
  22. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/requires.txt +0 -0
  23. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/TeLLMgramBot.egg-info/top_level.txt +0 -0
  24. {tellmgrambot-3.2.0 → tellmgrambot-3.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -12,7 +12,7 @@ from .providers.factory import get_provider
12
12
  from .providers.base import ContextLengthExceededError, ProviderAuthError, ProviderConnectionError
13
13
  from .initialize import INIT_BOT_CONFIG, ApiKeyStatus, init_structure, init_bot_config, init_bot_prompt
14
14
  from .conversation import Conversation
15
- from .database import get_private_mode, set_private_mode, delete_messages_for_user, delete_messages_for_chat
15
+ from .database import get_private_mode, set_private_mode, delete_messages_for_user, delete_messages_for_chat, delete_private_messages_for_user
16
16
  from .models import TokenLimits
17
17
  from .message_handlers import handle_greetings, handle_common_queries, handle_url_ask
18
18
  from .utils import read_yaml, read_text, exact_word_match, generate_error_path, log_error
@@ -96,7 +96,7 @@ class TelegramBot:
96
96
  In group chats: deletes the user's rows across all chats (private included).
97
97
 
98
98
  In both cases, evicts only the in-memory Conversation objects that contain this
99
- user's data scoped to the current chat, the user's private chat, and any other
99
+ user's data - scoped to the current chat, the user's private chat, and any other
100
100
  chats where their context was previously merged. Other users' active sessions are
101
101
  not affected. All private-mode notices for this user are also cleared.
102
102
  """
@@ -128,15 +128,16 @@ class TelegramBot:
128
128
 
129
129
  async def tele_private_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
130
130
  """
131
- Toggle private mode for the requesting user (private chats only).
131
+ Enable or disable private mode for the requesting user (private chats only).
132
132
 
133
133
  When private mode is ON, messages sent in private chats are flagged as is_private=1
134
- in the database and excluded when building context for group conversations. This lets
135
- users keep sensitive private exchanges out of shared group contexts.
134
+ in the database. These rows are excluded from all context loads - both group context
135
+ (cross-pollination) and private chat reloads. On /private off, they are permanently
136
+ deleted from the DB and purged from in-session memory (neuralyzer semantics).
136
137
 
137
138
  Usage:
138
- /private toggles private mode on (if off) or off (if on)
139
- /private off explicitly disables private mode
139
+ /private - enables private mode (idempotent if already on)
140
+ /private off - disables private mode and purges in-session private messages
140
141
 
141
142
  Only available in private chats; sending the command in a group returns an error.
142
143
  """
@@ -150,20 +151,26 @@ class TelegramBot:
150
151
  return
151
152
 
152
153
  user_id = user.id
154
+ chat_id = chat.id
153
155
  args = (msg.text or "").strip().split()
154
156
  force_off = len(args) > 1 and args[1].lower() == 'off'
155
- current = await get_private_mode(user_id)
156
- new_state = False if (force_off or current) else True
157
- await set_private_mode(user_id, new_state)
158
157
 
159
- if new_state:
158
+ if force_off:
159
+ # Neuralyzer: delete before flipping the mode flag so that if the delete
160
+ # fails, private mode stays ON and the user can safely retry /private off.
161
+ await delete_private_messages_for_user(user_id)
162
+ await set_private_mode(user_id, False)
163
+ # Also purge from in-memory conversation so the bot cannot reference them now.
164
+ if chat_id in self.conversations:
165
+ self.conversations[chat_id].purge_private_messages()
160
166
  await msg.reply_text(
161
- "Private mode is ON. Future messages in this private chat will be excluded "
162
- "from group conversation contexts. Use /private off to disable."
167
+ "Private mode is OFF. Future messages in this private chat will be included in group conversation contexts."
163
168
  )
164
169
  else:
170
+ await set_private_mode(user_id, True)
165
171
  await msg.reply_text(
166
- "Private mode is OFF. Future messages in this private chat will be included in group conversation contexts."
172
+ "Private mode is ON. Future messages in this private chat will be excluded "
173
+ "from group conversation contexts. Use /private off to disable."
167
174
  )
168
175
 
169
176
  async def tele_handle_response(self, text: str, msg: Message) -> str:
@@ -223,16 +230,16 @@ class TelegramBot:
223
230
  token_budget = floor(self.chatgpt['prune_threshold'] / 2)
224
231
 
225
232
  if user_id not in conv._context_cursor:
226
- # First appearance of this user in this session load their cross-chat history.
233
+ # First appearance of this user in this session - load their cross-chat history.
227
234
  # Pass bot_id so private chats can also load shared group context.
228
235
  # If no history exists yet, the cursor stays unset so the next message retries.
229
236
  await conv.get_past_interaction(token_budget, user_id, self.telegram['bot_id'])
230
237
  else:
231
- # Already loaded check for new cross-chat messages since last load.
238
+ # Already loaded - check for new cross-chat messages since last load.
232
239
  await conv.refresh_user_context(user_id, token_budget)
233
240
 
234
241
  # Add the user's message to our conversation, respecting private mode
235
- # Private mode only applies in private chats group messages are never flagged private
242
+ # Private mode only applies in private chats - group messages are never flagged private
236
243
  user_private_mode = await get_private_mode(user_id)
237
244
  is_private = (chat_type == 'private') and user_private_mode
238
245
  await conv.add_user_message(text, user_id, username, is_private)
@@ -246,7 +253,7 @@ class TelegramBot:
246
253
  if warn_key not in self._private_mode_warned:
247
254
  self._private_mode_warned.add(warn_key)
248
255
  private_mode_notice = (
249
- "\n\n(Note: your private mode is ON messages from our private chat "
256
+ "\n\n(Note: your private mode is ON - messages from our private chat "
250
257
  "are not included in this group's context. To disable, send /private off in our DMs.)"
251
258
  )
252
259
 
@@ -281,7 +288,7 @@ class TelegramBot:
281
288
 
282
289
  # Add the full reply (including any warning) to the conversation.
283
290
  # Propagate is_private so bot replies in private-mode exchanges are also excluded
284
- # from group context loading otherwise the assistant's half of the conversation leaks.
291
+ # from group context loading - otherwise the assistant's half of the conversation leaks.
285
292
  await conv.add_assistant_message(
286
293
  reply, self.telegram['bot_id'], self.telegram['username'], is_private
287
294
  )
@@ -52,17 +52,22 @@ class Conversation:
52
52
  self.system_model = TokenLimits(system_model)
53
53
  self.messages = [{"role": "system", "content": system_content}]
54
54
 
55
- # Maps user_id max message id at the time their cross-chat context was loaded.
55
+ # Maps user_id -> max message id at the time their cross-chat context was loaded.
56
56
  # Used for both per-user idempotency (is the user loaded?) and mid-session refresh
57
57
  # detection (have new cross-chat messages appeared since the last load?).
58
58
  self._context_cursor: dict[int, int] = {}
59
59
 
60
- # Maps user_id len(self.messages) after their history was loaded.
60
+ # Maps user_id -> len(self.messages) after their history was loaded.
61
61
  # Marks the boundary between historical context and live session messages so that
62
62
  # refresh_user_context() inserts delta rows at the right position (after history,
63
63
  # before live messages) rather than at index 1 (before all existing history).
64
64
  self._history_end: dict[int, int] = {}
65
65
 
66
+ # Set of id()s for message dicts added while private mode was ON.
67
+ # Used by purge_private_messages() to remove them from self.messages without
68
+ # touching the database. Cleared on purge or clear_interaction().
69
+ self._private_message_ids: set[int] = set()
70
+
66
71
  def set_system_content(self, new_content: str):
67
72
  """
68
73
  Replace the system content (bot's personality prompt) in the conversation.
@@ -83,9 +88,13 @@ class Conversation:
83
88
  content: The message text.
84
89
  user_id: Telegram user ID of the sender.
85
90
  username: Telegram username (display only, may be None).
86
- is_private: If True, excludes this message from group context loading (used for /forget or private commands).
91
+ is_private: If True, flags this message as private-mode excluded from
92
+ all context loads (user has /private ON in a private chat).
87
93
  """
88
- self.messages.append({"role": "user", "content": content})
94
+ msg_dict = {"role": "user", "content": content}
95
+ self.messages.append(msg_dict)
96
+ if is_private:
97
+ self._private_message_ids.add(id(msg_dict))
89
98
  await insert_message(self.chat_id, user_id, username, "user", content, is_private)
90
99
 
91
100
  async def add_assistant_message(self, content: str, bot_user_id: int, bot_username: str | None, is_private: bool = False):
@@ -100,7 +109,10 @@ class Conversation:
100
109
  the is_private flag of the user message it responds to, so that both
101
110
  sides of a private-mode exchange are excluded from shared contexts.
102
111
  """
103
- self.messages.append({"role": "assistant", "content": content})
112
+ msg_dict = {"role": "assistant", "content": content}
113
+ self.messages.append(msg_dict)
114
+ if is_private:
115
+ self._private_message_ids.add(id(msg_dict))
104
116
  await insert_message(self.chat_id, bot_user_id, bot_username, "assistant", content, is_private)
105
117
 
106
118
  async def get_message_token_count(self) -> int:
@@ -124,7 +136,7 @@ class Conversation:
124
136
  Remove oldest user-assistant messages until token count is below threshold.
125
137
 
126
138
  Recursively removes messages starting from index 1 (preserving the system message at index 0)
127
- until the conversation fits within the token limit. The database is not modified pruning
139
+ until the conversation fits within the token limit. The database is not modified - pruning
128
140
  only affects the in-memory context window for the current session.
129
141
 
130
142
  Args:
@@ -138,6 +150,23 @@ class Conversation:
138
150
  except Exception as e:
139
151
  log_error(e, f"{type(e).__name__} pruning under {token_limit} tokens for {self.chat_print}", self.error_log)
140
152
 
153
+ def purge_private_messages(self):
154
+ """
155
+ Remove all in-session private-mode messages from the in-memory conversation.
156
+
157
+ Strips any messages added while private mode was ON from self.messages without
158
+ modifying the database. Called when a user disables private mode (/private off)
159
+ so the bot can no longer reference messages sent during the private session.
160
+
161
+ Side Effects:
162
+ - Modifies self.messages in-place, removing tracked private message dicts.
163
+ - Clears self._private_message_ids.
164
+ """
165
+ if not self._private_message_ids:
166
+ return
167
+ self.messages = [m for m in self.messages if id(m) not in self._private_message_ids]
168
+ self._private_message_ids.clear()
169
+
141
170
  async def get_past_interaction(self, token_limit: int, user_id: int, bot_id: int | None = None) -> bool:
142
171
  """
143
172
  Load past conversation messages from the database up to a token limit.
@@ -145,10 +174,9 @@ class Conversation:
145
174
  Implements bidirectional cross-pollination: loads all messages from the current chat
146
175
  (all participants) plus all messages from the user's private chat (both user and
147
176
  assistant turns), merged chronologically. This eliminates amnesia when a user switches
148
- between private and group contexts.
149
-
150
- For group and supergroup chats, messages marked as private (is_private=1) are excluded
151
- so private-mode messages don't surface in shared contexts.
177
+ between private and group contexts. Messages marked as private (is_private=1) are
178
+ always excluded, regardless of chat type, ensuring private-mode exchanges never leak
179
+ into any session context.
152
180
 
153
181
  For private chats, if bot_id is provided and remaining token budget allows, also loads
154
182
  full message history from any groups shared between the user and the bot. This lets the
@@ -181,7 +209,7 @@ class Conversation:
181
209
  print(f"Context already loaded for User {user_id} in {self.chat_print}")
182
210
  return True
183
211
 
184
- exclude_private = self.chat_type in ('group', 'supergroup')
212
+ exclude_private = True
185
213
  rows = await load_full_user_context(user_id, self.chat_id, exclude_private=exclude_private)
186
214
 
187
215
  token_limit_reached = False
@@ -231,7 +259,7 @@ class Conversation:
231
259
  label = "Full" if not token_limit_reached and not group_limit_reached else "Partial"
232
260
  group_note = f", plus {group_rows_inserted} group message(s)" if group_rows_inserted > 0 else ""
233
261
  print(
234
- f"{label} context loaded for User {user_id} in {self.chat_print} "
262
+ f"{label} context loaded for User {user_id} in {self.chat_print} - "
235
263
  f"storing {token_count} token(s){group_note}"
236
264
  )
237
265
  else:
@@ -267,7 +295,7 @@ class Conversation:
267
295
  if user_id not in self._context_cursor:
268
296
  return False
269
297
 
270
- exclude_private = self.chat_type in ('group', 'supergroup')
298
+ exclude_private = True
271
299
  current_max = await get_max_cross_chat_message_id(user_id, self.chat_id, exclude_private)
272
300
 
273
301
  if current_max <= self._context_cursor[user_id]:
@@ -314,11 +342,13 @@ class Conversation:
314
342
  - Clears self.messages to contain only the system message (index 0).
315
343
  - Resets self._context_cursor to empty dict.
316
344
  - Resets self._history_end to empty dict.
345
+ - Resets self._private_message_ids to empty set.
317
346
  - Deletes all messages for this chat (private) or this user (group) from database.
318
347
  """
319
348
  self.messages = [self.messages[0]]
320
349
  self._context_cursor = {}
321
350
  self._history_end = {}
351
+ self._private_message_ids = set()
322
352
  if self.chat_type == 'private':
323
353
  await delete_messages_for_chat(self.chat_id)
324
354
  else:
@@ -48,7 +48,7 @@ def get_db_path() -> str:
48
48
 
49
49
  Reads TELLMGRAMBOT_DATA_PATH env var (directory path) and appends conversations.db.
50
50
  Falls back to data/conversations.db relative to the execution directory if unset.
51
- Module-level _DB_PATH overrides everything used by tests.
51
+ Module-level _DB_PATH overrides everything - used by tests.
52
52
  """
53
53
  if _DB_PATH:
54
54
  return _DB_PATH
@@ -59,7 +59,7 @@ async def init_db() -> None:
59
59
  """
60
60
  Create the messages and private_mode tables if they do not exist.
61
61
  Enables WAL journal mode for safe concurrent reads and writes.
62
- Safe to call repeatedly all statements use IF NOT EXISTS.
62
+ Safe to call repeatedly - all statements use IF NOT EXISTS.
63
63
  """
64
64
  async with aiosqlite.connect(get_db_path()) as db:
65
65
  await db.executescript(_DDL)
@@ -160,26 +160,28 @@ async def load_full_user_context(
160
160
  In Telegram, a user's private chat_id always equals their user_id. Three arms cover
161
161
  all cross-context cases:
162
162
 
163
- Arm 1 chat_id = current_chat_id:
163
+ Arm 1 - chat_id = current_chat_id:
164
164
  All messages (user and bot) in the current chat. Always included.
165
165
 
166
- Arm 2 chat_id = user_id AND chat_id != current_chat_id:
166
+ Arm 2 - chat_id = user_id AND chat_id != current_chat_id:
167
167
  All messages (both sides) from the user's private chat, included when the
168
168
  current chat is a group. Pulling both sides restores proper role alternation
169
169
  that was broken when only the user's own rows were fetched.
170
170
 
171
- Arm 3 user_id = user_id AND chat_id != current_chat_id AND chat_id != user_id:
171
+ Arm 3 - user_id = user_id AND chat_id != current_chat_id AND chat_id != user_id:
172
172
  The requesting user's own messages from any other group chats (not the private
173
173
  chat, not the current chat). Covers group-to-private and group-to-group context.
174
174
  Only the user's own rows are fetched from other groups (not bot replies).
175
175
 
176
- The exclude_private filter is applied globally when the current chat is a group.
176
+ The exclude_private flag is controlled by the caller and applies regardless of chat type.
177
+ Conversation.get_past_interaction always passes exclude_private=True so that is_private=1
178
+ rows are never rehydrated into any session context.
177
179
 
178
180
  Args:
179
181
  user_id: Telegram user ID of the requesting user.
180
182
  current_chat_id: The chat_id of the current conversation (negative for groups, positive for private).
181
- exclude_private: If True, rows with is_private=1 are excluded from all chats.
182
- Applied when the current chat is a group so private-mode messages
183
+ exclude_private: If True, rows with is_private=1 are excluded from all arms of the query.
184
+ Always passed as True by Conversation.get_past_interaction so that private-mode messages
183
185
  don't surface in shared contexts.
184
186
 
185
187
  Returns:
@@ -243,7 +245,7 @@ async def load_new_cross_chat_context(
243
245
  Args:
244
246
  user_id: Telegram user ID of the requesting user.
245
247
  current_chat_id: The chat_id of the current conversation.
246
- since_id: Exclusive lower bound only rows with id > since_id are returned.
248
+ since_id: Exclusive lower bound - only rows with id > since_id are returned.
247
249
  exclude_private: If True, rows with is_private=1 are excluded.
248
250
 
249
251
  Returns:
@@ -342,7 +344,7 @@ async def load_shared_group_context(
342
344
  async def delete_messages_for_user(user_id: int) -> None:
343
345
  """
344
346
  Delete all message rows for a given user_id across all chats.
345
- Used by /forget in group chats only the issuing user's rows are removed.
347
+ Used by /forget in group chats - only the issuing user's rows are removed.
346
348
  """
347
349
  async with aiosqlite.connect(get_db_path()) as db:
348
350
  await db.execute("DELETE FROM messages WHERE user_id = ?", (user_id,))
@@ -357,6 +359,27 @@ async def delete_messages_for_chat(chat_id: int) -> None:
357
359
  await db.execute("DELETE FROM messages WHERE chat_id = ?", (chat_id,))
358
360
  await db.commit()
359
361
 
362
+ async def delete_private_messages_for_user(user_id: int) -> None:
363
+ """
364
+ Delete all private-mode message rows for a user from their private chat.
365
+
366
+ Removes rows where chat_id = user_id (the user's Telegram private chat) AND
367
+ is_private = 1. This covers both the user's own messages and the bot's replies
368
+ stored in the same private chat, since assistant turns share the same chat_id.
369
+
370
+ Called when the user turns private mode off (/private off) to ensure that
371
+ private-mode messages do not resurface on the next session reload.
372
+
373
+ Args:
374
+ user_id: Telegram user ID; also serves as the private chat_id.
375
+ """
376
+ async with aiosqlite.connect(get_db_path()) as db:
377
+ await db.execute(
378
+ "DELETE FROM messages WHERE chat_id = ? AND is_private = 1",
379
+ (user_id,)
380
+ )
381
+ await db.commit()
382
+
360
383
  async def get_private_mode(user_id: int) -> bool:
361
384
  """
362
385
  Return whether private mode is currently enabled for this user.
@@ -112,7 +112,7 @@ def init_keys() -> ApiKeyStatus:
112
112
  """
113
113
  Load API keys and return a ApiKeyStatus indicating which features are available.
114
114
 
115
- Telegram key is always required bot exits if missing or invalid.
115
+ Telegram key is always required - bot exits if missing or invalid.
116
116
  All other keys (OpenAI, Anthropic, VirusTotal) are optional: missing keys
117
117
  disable the associated features rather than exiting.
118
118
 
@@ -159,7 +159,7 @@ def init_keys() -> ApiKeyStatus:
159
159
  f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
160
160
  "VirusTotal integration" if key == 'virustotal' else None]
161
161
  reason = ", ".join(w for w in which if w) or "related features"
162
- print(f"Warning: {key}.key not found {reason} will be disabled.")
162
+ print(f"Warning: {key}.key not found - {reason} will be disabled.")
163
163
  continue
164
164
  else:
165
165
  os.environ[env_var] = read_text(path)
@@ -173,7 +173,7 @@ def init_keys() -> ApiKeyStatus:
173
173
  f"url_model ({config.get('url_model', '?')})" if url_provider == key else None,
174
174
  "VirusTotal integration" if key == 'virustotal' else None]
175
175
  reason = ", ".join(w for w in which if w) or "related features"
176
- print(f"Warning: {env_var} is blank or invalid {reason} will be disabled.")
176
+ print(f"Warning: {env_var} is blank or invalid - {reason} will be disabled.")
177
177
  continue
178
178
 
179
179
  available.add(key)
@@ -348,8 +348,8 @@ def init_structure() -> ApiKeyStatus:
348
348
  - Configuration files (config.yaml, models.yaml) with inline parameter descriptions.
349
349
  - System prompt files (test_personality.prmpt, url_analysis.prmpt).
350
350
 
351
- Provider key requirements are inferred from config.yaml model prefixes (claude-* Anthropic,
352
- gpt-* OpenAI, etc.) to streamline setup for single-provider deployments.
351
+ Provider key requirements are inferred from config.yaml model prefixes (claude-* -> Anthropic,
352
+ gpt-* -> OpenAI, etc.) to streamline setup for single-provider deployments.
353
353
 
354
354
  Returns:
355
355
  ApiKeyStatus indicating which features are available based on loaded API keys.
@@ -9,8 +9,8 @@ def get_provider(model: str) -> LLMProvider:
9
9
  Return the appropriate LLM provider for the given model name.
10
10
 
11
11
  Provider is inferred from the model name prefix:
12
- - 'claude-' AnthropicProvider
13
- - anything else OpenAIProvider
12
+ - 'claude-' -> AnthropicProvider
13
+ - anything else -> OpenAIProvider
14
14
  """
15
15
  if model.startswith('claude-'):
16
16
  key = 'anthropic'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TeLLMgramBot
3
- Version: 3.2.0
3
+ Version: 3.2.2
4
4
  Summary: LLM-powered Telegram bot (OpenAI + Anthropic)
5
5
  Home-page: https://github.com/Digital-Heresy/TeLLMgramBot
6
6
  Author: Digital Heresy
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name='TeLLMgramBot',
8
- version='3.2.0',
8
+ version='3.2.2',
9
9
  packages=find_packages(),
10
10
  license='MIT',
11
11
  author='Digital Heresy',
File without changes
File without changes
File without changes