在 google.generativeai 的 model.generate_content() 函数中使用stream=True时动态编辑电报机器人消息

问题描述 投票:0回答:1

我正在使用

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
python formatting markdown aiogram google-generativeai
1个回答
0
投票

因此,在寻找解决方法之后,我想出了一个函数,可以关闭累积文本中所有打开的 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)

© www.soinside.com 2019 - 2024. All rights reserved.