microsoft / botbuilder-python

The Microsoft Bot Framework provides what you need to build and connect intelligent bots that interact naturally wherever your users are talking, from text/sms to Skype, Slack, Office 365 mail and other popular services.
http://botframework.com
MIT License
672 stars 271 forks source link

Bot gives same response multiple times when deployed with azure bot service #2086

Closed uprg closed 2 months ago

uprg commented 3 months ago

Version

v4

Describe the bug

Same responses from bot are coming multiple times when deployed with azure bot service

To Reproduce

Clone the echo bot Test it on emulator Deploy it on azure with azure bot service

Expected behavior

Response should only come once.

Additional context

I created a simple bot which can send messages based on a certain input like if you send hi it will send hello. Some messages like hi and hello are send from the bot itself via a if else statement Now some text with prefix question like "question: what is the number system" When bot gets these type of text it send a request to OpenAI APIs for an LLM reponse

Now both these kinds of response one which is normal from the bot and one which comes from openai to bot and then to user as response are coming multiple times.

I though i might have done something in async operations wrong but it also gives two response for both types of messages

Below is some code from app.py

import sys
import traceback
from datetime import datetime
from http import HTTPStatus

from aiohttp import web
from aiohttp.web import Request, Response, json_response
from botbuilder.core import (
    BotFrameworkAdapterSettings,
    TurnContext,
    BotFrameworkAdapter,
)
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes

from bots import EchoBot
from config import DefaultConfig
from utils import callOpenAI

CONFIG = DefaultConfig()

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD)
ADAPTER = BotFrameworkAdapter(SETTINGS)

# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
    # This check writes out errors to console log .vs. app insights.
    # NOTE: In production environment, you should consider logging this to Azure
    #       application insights.
    print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
    traceback.print_exc()

    # Send a message to the user
    await context.send_activity("The bot encountered an error or bug.")
    await context.send_activity(
        "To continue to run this bot, please fix the bot source code."
    )
    # Send a trace activity if we're talking to the Bot Framework Emulator
    if context.activity.channel_id == "emulator":
        # Create a trace activity that contains the error object
        trace_activity = Activity(
            label="TurnError",
            name="on_turn_error Trace",
            timestamp=datetime.utcnow(),
            type=ActivityTypes.trace,
            value=f"{error}",
            value_type="https://www.botframework.com/schemas/error",
        )
        # Send a trace activity, which will be displayed in Bot Framework Emulator
        await context.send_activity(trace_activity)

ADAPTER.on_turn_error = on_error

# Create the Bot
BOT = EchoBot()

# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
    # Main bot message handler.
    if "application/json" in req.headers["Content-Type"]:
        body = await req.json()
    else:
        return Response(status=HTTPStatus.UNSUPPORTED_MEDIA_TYPE)
    print("\n message endpoint body: ", body)

    if body["type"] == "message":
        print("body type message")
        status = await check_text(body["from"]["id"], body["text"])

        if status["isQuestion"] == False:
            body["text"] = status["text"]
            activity = Activity().deserialize(body)
            auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
            await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
        else:
              callOpenAI(status["text"])
             body["text"] = status["text"]
             activity = Activity().deserialize(body)
             auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
             await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)

APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)

if __name__ == "__main__":
    try:
        web.run_app(APP, host="0.0.0.0", port=CONFIG.PORT)
    except Exception as error:
        raise error

Below is my Bot code

from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.core.teams.teams_activity_handler import TeamsActivityHandler
from botbuilder.schema import Activity, ActivityTypes

class EchoBot(ActivityHandler):

    async def on_message_activity(self, turn_context: TurnContext):
              activity = Activity(
                text=f"{turn_context.activity.text}", 
                type=ActivityTypes.message, 

            )

            await turn_context.send_activity(activity)
stevkan commented 2 months ago

@uprg - Thank you for your patience. Are you still having an issue with this? My initial guess to the cause of the problem is with Emulator which depends Web Chat. Web Chat, at this point, is not setup to work with streaming responses like those generated from something like ChatGPT. What is more, Emulator is using an older version of Web Chat. This needs to be updated to current version, however there is no ETA on that, at the moment.

If you use another client that isn't Web Chat based, does the issue persist there? If you perform logging in App.py on the if...else statement, are you certain it is accurately filtering the incoming text between questions and non-questions? To that end, since it is an echo bot, is it repeating responses because the text in both the original message as well as in the echoed response are virtually the same?

uprg commented 2 months ago

@stevkan Thank you for responding.

stevkan commented 2 months ago

@uprg - Ok, I will attempt a repro of the issue using the code above, providing I'm not missing anything.

tracyboehrer commented 2 months ago

There isn't enough info here to make a firm determination. However, I question how this is done. Typically, the 'message' implementation would just call 'process_activity'. The the ActivityHandler (the bot) would handle the business logic.

Given:

        if status["isQuestion"] == False:
            body["text"] = status["text"]
            activity = Activity().deserialize(body)
            auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
            await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
        else:
              callOpenAI(status["text"])
             body["text"] = status["text"]
             activity = Activity().deserialize(body)
             auth_header = req.headers["Authorization"] if "Authorization" in req.headers else ""
             await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)

Whatever 'callOpenAI' does would not stop the EchoBot.on_message_activity from executing.

1) isQuestion == False: EchoBot sends back (echo's) whatever the user sent 2) isQuestion == true: Something happens is 'callOpenAI', then EchoBot sends back whatever the user sent

Check: The indentation on the line with 'callOpenAI'