Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ An AI-powered Telegram bot playing "el gordo" — a blunt, politically incorrect

## Features

- **AI chat**: configurable personality with web search, powered by Qwen via OpenRouter
- **AI chat**: configurable personality with web search, powered by DeepSeek via OpenRouter
- **Streaming responses**: AI replies stream token-by-token to Telegram (when no tools are active)
- **Chat memory with RediSearch**: persistent conversation history, full-text search, automatic compaction
- **Incremental summaries**: `/resumen` streams conversation summaries using Minimax, with automatic context compaction
- **Incremental summaries**: `/resumen` streams conversation summaries using DeepSeek, with automatic context compaction
- **Agentic tools**: AI can call tools (price lookup, calculator, web fetch, task scheduling) via function calling
- **Market data**: `/prices`, `/usd`, `/petroleo`, `/devo`, `/powerlaw`, `/rainbow`, `/rulo`, `/eleccion`
- **BCRA economic data**: `/bcra`, `/variables`
Expand Down Expand Up @@ -52,10 +52,10 @@ uv run --locked python run_polling.py

| Use | Provider | Model |
|---|---|---|
| **Chat** | OpenRouter | `qwen/qwen3.6-plus` |
| **Chat** | OpenRouter | `~deepseek/deepseek-v4-flash-latest` |
| **Vision** | OpenRouter | `google/gemini-3.1-flash-lite-preview` |
| **Transcription** | Groq → OpenRouter fallback | `whisper-large-v3` → `google/gemini-3.1-flash-lite-preview` |
| **Summary** | OpenRouter | `minimax/minimax-m2.7` |
| **Summary** | OpenRouter | `~deepseek/deepseek-v4-flash-latest` |

**Streaming**: token streaming only when no tools/web-search are active. Tool-enabled requests return complete responses.

Expand Down
8 changes: 4 additions & 4 deletions api/ai/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"input_per_million": 250_000,
"output_per_million": 1_500_000,
},
"deepseek/deepseek-v4-flash": {
"~deepseek/deepseek-v4-flash-latest": {
"input_per_million": 400_000,
"output_per_million": 1_200_000,
},
Expand All @@ -40,7 +40,7 @@
def chat_output_token_limit(model: str) -> int:
"""Return a larger budget only for chat models that use hidden reasoning."""

if str(model or "").split(":", 1)[0] == "deepseek/deepseek-v4-flash":
if str(model or "").split(":", 1)[0] == "~deepseek/deepseek-v4-flash-latest":
return REASONING_CHAT_OUTPUT_TOKEN_LIMIT
return CHAT_OUTPUT_TOKEN_LIMIT

Expand Down Expand Up @@ -137,9 +137,9 @@ def estimate_chat_reserve_credits(
messages: Sequence[Mapping[str, Any]],
max_output_tokens: Optional[int] = None,
extra_input_tokens: int = 0,
model: str = "deepseek/deepseek-v4-flash",
model: str = "~deepseek/deepseek-v4-flash-latest",
) -> int:
pricing = MODEL_PRICING_USD_MICROS.get(model, MODEL_PRICING_USD_MICROS["deepseek/deepseek-v4-flash"])
pricing = MODEL_PRICING_USD_MICROS.get(model, MODEL_PRICING_USD_MICROS["~deepseek/deepseek-v4-flash-latest"])
output_token_limit = (
chat_output_token_limit(model)
if max_output_tokens is None
Expand Down
4 changes: 2 additions & 2 deletions api/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ def _parse_timeframe(

def make_chat_tz(offset: int = -3) -> timezone:
return timezone(timedelta(hours=offset))
PRIMARY_CHAT_MODEL = "deepseek/deepseek-v4-flash"
SUMMARY_MODEL = "deepseek/deepseek-v4-flash"
PRIMARY_CHAT_MODEL = "~deepseek/deepseek-v4-flash-latest"
SUMMARY_MODEL = "~deepseek/deepseek-v4-flash-latest"
SUMMARY_MAX_TOKENS = 2048
COMPACTION_THRESHOLD = 40
COMPACTION_KEEP = 25
Expand Down
4 changes: 2 additions & 2 deletions benchmark_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ def __init__(self, api_key: str):
self.models = [
"qwen/qwen3.6-plus",
"minimax/minimax-m2.7",
"deepseek/deepseek-v4-flash",
"~deepseek/deepseek-v4-flash-latest",
]

self.model_pricing = {
"qwen/qwen3.6-plus": {"input": 0.325, "output": 1.95, "context": "1M"},
"minimax/minimax-m2.7": {"input": 0.30, "output": 1.20, "context": "196K"},
"deepseek/deepseek-v4-flash": {"input": 0.40, "output": 1.20, "context": "2M"},
"~deepseek/deepseek-v4-flash-latest": {"input": 0.40, "output": 1.20, "context": "1M"},
}

def load_bot_config(self) -> Dict[str, Any]:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_admin_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ def test_admin_report_formats_additional_context_in_english():
index.app_runtime.admin.report(
"OpenRouter unexpected finish_reason=None",
extra_context={
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"enable_web_search": True,
},
)

assert send_msg.call_args.args[1] == (
"admin report from VPS: OpenRouter unexpected finish_reason=None"
"\n\nadditional context:"
"\nmodel: deepseek/deepseek-v4-flash"
"\nmodel: ~deepseek/deepseek-v4-flash-latest"
"\nenable_web_search: True"
)

Expand Down
56 changes: 28 additions & 28 deletions tests/test_ai_billing_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ def test_build_insufficient_credits_message_mentions_group_balances():

def test_chat_output_token_limit_is_model_specific():
assert (
chat_output_token_limit("deepseek/deepseek-v4-flash")
chat_output_token_limit("~deepseek/deepseek-v4-flash-latest")
== REASONING_CHAT_OUTPUT_TOKEN_LIMIT
== 8192
)
assert chat_output_token_limit("deepseek/deepseek-v4-flash:exacto") == 8192
assert chat_output_token_limit("~deepseek/deepseek-v4-flash-latest:exacto") == 8192
assert chat_output_token_limit("other/model") == CHAT_OUTPUT_TOKEN_LIMIT == 1024


Expand All @@ -76,7 +76,7 @@ def test_calculate_billing_for_segments_applies_cached_token_discount():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 1_000,
"input_cached_tokens": 900,
Expand All @@ -92,7 +92,7 @@ def test_calculate_billing_for_segments_applies_cached_token_discount():
assert breakdown["charged_credits_display"] == "0.2"
assert breakdown["model_breakdown"] == [
{
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usd_micros": 1_000,
"input_tokens": 1_000,
"input_cached_tokens": 900,
Expand All @@ -107,12 +107,12 @@ def test_calculate_billing_for_segments_normalizes_billing_model_ids():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {"input_tokens": 100, "output_tokens": 50},
},
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {"input_tokens": 100, "output_tokens": 50},
},
{
Expand All @@ -135,8 +135,8 @@ def test_calculate_billing_for_segments_normalizes_billing_model_ids():

assert breakdown["raw_usd_micros"] > 0
assert [item["model"] for item in breakdown["model_breakdown"]] == [
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-flash",
"~deepseek/deepseek-v4-flash-latest",
"~deepseek/deepseek-v4-flash-latest",
"google/gemini-3.1-flash-lite-preview",
"google/gemini-3.1-flash-lite-preview",
"groq/whisper-large-v3",
Expand All @@ -148,7 +148,7 @@ def test_calculate_billing_for_segments_bumps_pricing_version_for_deepseek_searc
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {"input_tokens": 100, "output_tokens": 50},
"metadata": {"web_search_requests": 1},
}
Expand All @@ -163,7 +163,7 @@ def test_calculate_billing_for_segments_reads_cached_tokens_from_prompt_token_de
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"prompt_tokens": 2_000,
"completion_tokens": 100,
Expand All @@ -178,7 +178,7 @@ def test_calculate_billing_for_segments_reads_cached_tokens_from_prompt_token_de
assert breakdown["charged_credits_display"] == "0.2"
assert breakdown["model_breakdown"] == [
{
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usd_micros": 920,
"input_tokens": 2_000,
"input_cached_tokens": 1_500,
Expand All @@ -194,7 +194,7 @@ def test_calculate_billing_for_segments_skips_cached_source_segments():
{
"kind": "chat",
"source": "cache",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 10_000,
"output_tokens": 500,
Expand All @@ -216,7 +216,7 @@ def test_calculate_billing_for_segments_bills_web_search_requests():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {"input_tokens": 100, "output_tokens": 50},
"metadata": {"web_search_requests": 2},
}
Expand All @@ -235,7 +235,7 @@ def test_calculate_billing_for_segments_refunds_cache_only_usage_to_zero():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"source": "cache",
"usage": {
"input_tokens": 10_000,
Expand Down Expand Up @@ -288,7 +288,7 @@ def test_settle_reserved_ai_credits_refunds_successful_unused_reserve():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 100,
"output_tokens": 50,
Expand Down Expand Up @@ -318,7 +318,7 @@ def test_settle_reserved_ai_credits_charges_extra_when_actual_exceeds_reserve():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 4000,
"output_tokens": 3000,
Expand Down Expand Up @@ -404,7 +404,7 @@ def test_settle_reserved_ai_credits_records_debt_when_extra_charge_fails():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 4000,
"output_tokens": 3000,
Expand Down Expand Up @@ -447,7 +447,7 @@ def test_settle_reserved_ai_credits_batch_converts_to_credits_once_and_refunds_o
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 1,
"output_tokens": 1,
Expand Down Expand Up @@ -492,7 +492,7 @@ def test_settle_reserved_ai_credits_batch_mixed_sources_refunds_later_reserves()
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 100,
"output_tokens": 50,
Expand Down Expand Up @@ -606,7 +606,7 @@ def test_settle_reserved_ai_credits_batch_charges_extra_once_when_total_exceeds_
},
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 4000,
"output_tokens": 2000,
Expand Down Expand Up @@ -635,7 +635,7 @@ def test_settle_reserved_ai_credits_keeps_reserve_when_groq_reports_zero_usage()
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 0,
"output_tokens": 0,
Expand Down Expand Up @@ -669,7 +669,7 @@ def test_settle_reserved_ai_credits_refunds_cache_only_usage():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"prompt_tokens": 1_000,
"prompt_tokens_details": {"cached_tokens": 900},
Expand Down Expand Up @@ -712,7 +712,7 @@ def test_settle_reserved_ai_credits_batch_keeps_full_reserve_when_total_usage_is
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 0,
"output_tokens": 0,
Expand Down Expand Up @@ -763,7 +763,7 @@ def test_settle_reserved_ai_credits_batch_ignores_none_segments():
None,
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 100,
"output_tokens": 50,
Expand Down Expand Up @@ -839,7 +839,7 @@ def test_settle_reserved_ai_credits_refunds_partial_chat_usage():
segments = [
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"input_tokens": 1_000,
"output_tokens": 100,
Expand Down Expand Up @@ -929,7 +929,7 @@ def test_calculate_billing_uses_gateway_cost_when_higher_than_local():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"prompt_tokens": 100,
"completion_tokens": 50,
Expand All @@ -951,7 +951,7 @@ def test_calculate_billing_keeps_local_cost_when_higher_than_gateway():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"prompt_tokens": 4_000,
"completion_tokens": 2_000,
Expand All @@ -970,7 +970,7 @@ def test_calculate_billing_without_gateway_cost_uses_local():
[
{
"kind": "chat",
"model": "deepseek/deepseek-v4-flash",
"model": "~deepseek/deepseek-v4-flash-latest",
"usage": {
"prompt_tokens": 100,
"completion_tokens": 50,
Expand Down
8 changes: 4 additions & 4 deletions tests/test_ai_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def test_log_groq_request_result_logs_local_billing_details():
result = AIUsageResult(
kind="chat",
text="respuesta",
model="deepseek/deepseek-v4-flash",
model="~deepseek/deepseek-v4-flash-latest",
usage={"input_tokens": 100, "output_tokens": 50},
metadata={"groq_account": "primary"},
)
Expand Down Expand Up @@ -183,7 +183,7 @@ def fake_attempt(account):
return AIUsageResult(
kind="chat",
text="respuesta chat",
model="deepseek/deepseek-v4-flash",
model="~deepseek/deepseek-v4-flash-latest",
metadata={"groq_account": account},
)

Expand Down Expand Up @@ -548,7 +548,7 @@ def test_complete_with_providers_openrouter_success():
openrouter_result = AIUsageResult(
kind="chat",
text="OpenRouter response",
model="deepseek/deepseek-v4-flash",
model="~deepseek/deepseek-v4-flash-latest",
usage={"input_tokens": 100, "output_tokens": 50},
metadata={"provider": "openrouter"},
)
Expand Down Expand Up @@ -597,7 +597,7 @@ def test_complete_with_providers_records_openrouter_billing_on_success(monkeypat
openrouter_result = AIUsageResult(
kind="chat",
text="OpenRouter response",
model="deepseek/deepseek-v4-flash",
model="~deepseek/deepseek-v4-flash-latest",
usage={"input_tokens": 100, "output_tokens": 50},
metadata={"provider": "openrouter"},
)
Expand Down
Loading