我正在使用
aiogram
开发一个电报机器人,它将用户的消息作为提示并使用 google-generativeai
GenerativeModel 生成响应。它在正常模式下工作正常,我等待完整的响应,但我想使用 stream=True
参数生成响应块,并使用 message.edit_text()
函数将新块添加到电报消息中。问题是我遇到了一个错误:
aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 73
这是我的功能:
from aiogram import Bot, Dispatcher, Router, types
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from google.api_core.exceptions import BadRequest as GoogleBadRequest
from app.database import engine
from app.models import Language, TelegramUser
from app.services.ai import generate_response
from app.services.users import get_or_create_telegram_user, get_translated_message as _, refresh_last_interaction_date
bot = Bot(token=TOKEN,
default=DefaultBotProperties())
dp = Dispatcher()
telegram_router = Router()
dp.include_router(telegram_router)
...
@dp.message()
async def handle_prompt(message: types.Message):
async with AsyncSession(engine) as db:
user, __ = await get_or_create_telegram_user(message, db)
prompt = message.text
temp_message_text = await _("timer_1", user.language_id, db)
temp_message = await message.answer(temp_message_text)
# Accumulate the complete response
accumulated_text = ""
try:
async for chunk in generate_response(prompt):
accumulated_text += chunk
# Send the complete response with correct formatting
await temp_message.edit_text(
accumulated_text,
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN
)
except Exception as e:
print(f"Error in handle_prompt: {e}")
await temp_message.edit_text("An error occurred during processing.")
finally:
await refresh_last_interaction_date(user.chat_id, db)
# ai.py
from typing import AsyncGenerator
import google.generativeai as genai
from app.constants import GEMINI_API_KEY
from app.utils.helpers import sync_to_async_iterable
# Set up the Gemini API configuration
genai.configure(api_key=GEMINI_API_KEY)
# Initialize the Gemini model
model = genai.GenerativeModel(model_name="gemini-1.5-flash")
async def generate_response(prompt: str, **kwargs) -> AsyncGenerator[str, None]:
try:
response = sync_to_async_iterable(model.generate_content(prompt, stream=True, **kwargs))
async for content in response:
yield content.text
except Exception as e:
print(f"Error in generate_response: {e}")
yield "An error occurred during processing." # Yield an error message
问题是,当我收到响应块时,有时块中会出现 markdown 开始符号,但结束符号却没有。如果发生这种情况,则会出现错误
aiogram.exceptions.TelegramBadRequest: Telegram server says - Bad Request: can't parse entities: Can't find end of the entity starting at byte offset ..
。我已经尝试了ParseMode.MARKDOWN
和ParseMode.MARKDOWN_V2
,但问题是一样的。如果引发此异常,我尝试删除 parse_mode 参数,但它似乎没有帮助。
我发现generate_contents()返回一个格式为
markdown2
的文本,并且Telegram需要严格的格式。
我对 Markdown 格式和异步编程不太熟悉,我需要帮助解决这个问题。
Python版本:
3.10.8
aiogram==3.15.0
google-generativeai==0.8.3
因此,在寻找解决方法之后,我想出了一个函数,可以关闭累积文本中所有打开的 Markdown 标签,以确保 Telegram 不会引发解析错误。
# helpers.py
def ensure_valid_markdown(text: str) -> str:
stack = [] # Stack to track opening tags
result = [] # Accumulated characters
i = 0 # Current character index
# Markdown symbols: single-character and multi-character
single_char_symbols = {'*', '`', '~'}
multi_char_symbols = {'```'}
while i < len(text):
# Handle multi-character tags
if text[i:i + 3] in multi_char_symbols:
if stack and stack[-1] == '```': # Close an open code block
stack.pop()
result.append('```')
i += 3
else: # Open a new code block
stack.append('```')
result.append('```')
i += 3
elif text[i] in single_char_symbols: # Handle single-character tags
if stack and stack[-1] == text[i]: # Close the tag
stack.pop()
result.append(text[i])
else: # Open a new tag
stack.append(text[i])
result.append(text[i])
i += 1
else:
# Append normal characters
result.append(text[i])
i += 1
# Close any unbalanced tags at the end of the stream
while stack:
unmatched = stack.pop()
if unmatched == '```':
result.append('```') # Close a code block
else:
result.append(unmatched) # Close single-character tag
return ''.join(result)
# main.py
from app.utils.helpers import ensure_valid_markdown
...
@dp.message()
async def handle_prompt(message: types.Message):
async with AsyncSession(engine) as db:
user, __ = await get_or_create_telegram_user(message, db)
prompt = message.text
temp_message_text = await _("timer_1", user.language_id, db)
temp_message = await message.answer(temp_message_text)
# Accumulate the complete response
accumulated_text = ""
try:
async for chunk in generate_response(prompt):
accumulated_text += chunk
valid_markdown_text = ensure_valid_markdown(accumulated_text) # make sure each markdown tag is closed
# Send the complete response with correct formatting
await temp_message.edit_text(
valid_markdown_text, # send a valid markdown text
disable_web_page_preview=True,
parse_mode=ParseMode.MARKDOWN
)
except Exception as e:
print(f"Error in handle_prompt: {e}")
await temp_message.edit_text("An error occurred during processing.")
finally:
await refresh_last_interaction_date(user.chat_id, db)