Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frevagpt #69

Open
wants to merge 137 commits into
base: main
Choose a base branch
from
Open

Frevagpt #69

wants to merge 137 commits into from

Conversation

BiancaWentzel
Copy link

@Karinon Could you have a look at the whole frevagpt and suggest changes and improvements?
I don't really want to merge the changes to main as long as the feature is still in development but a genereal look at the codebase would be helpful.

Thank you

@antarcticrainforest
Copy link
Member

We need to hammer out a proper deployment strategy. Currently, I am quite doubtful that we have a mild plan for shipping the whole thing. I haven't even seen a PR/MR for the backend.

Copy link
Collaborator

@Karinon Karinon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part one. I will comment more stuff later.

resolve: {
fullySpecified: false
}
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this also an old fragment? I couldn't find a purpose.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be the case

buffer: require.resolve("buffer"),
},
},
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the changes are necessary anymore except for maybe Dotenv (which could be moved to webpack.base.config.js). Even this one would not be necessary when you apply all the changes I recommend, but I guess you can leave it here, as I am sure it will come in handy another time (I had already some ideas)..

package.json Outdated
"react-table": "^7.7.0",
"react-window": "^1.8.8",
"redux": "^4.2.1",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.1",
"react-select": "5.7.3"
"remark-gfm": "^3.0.1",
"stream-browserify": "^3.0.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream-browserify, JSONStream and buffer are no longer necessary.

I would also propose to remove bootstrap-icons. For this purpose we already have react-icons (https://react-icons.github.io/react-icons/) which you should use instead. Consider to use the FontAwesome Set there, as this is what I was using for everything else (with a few minor exceptions) to keep things consistent.

@@ -281,6 +298,12 @@ def _set_favicon(html_color: str, project_root: Path) -> None:
"variable",
]
MENU_ENTRIES = []
CHAT_BOT_URL = "http://vader5-icpub.lvt.dkrz.de:8502"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be configurable via os.environ.get. @antarcticrainforest is this how you want this? I lost track when we are using web_config.get and when os.environ.get

@@ -77,6 +77,23 @@ def _get_logo(logo_file, project_root):
return f"/static/img/{logo_file.name}"


def _read_secret(port: int = 5002, key: str = "email") -> dict[str, str]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antarcticrainforest I have no clue about those changes and where they come from. Do we need this? Apparently it is not in use.

return codeSnippets;
}

export const objectToQueryString = (obj) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a dependency query-string which does essentially the same thing. Use that instead

const response = await fetch("/api/chatbot/ping");
if (response.status === 200) pingSuccessful = true;
} catch (err) {
console.error("PingError: ", err);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

console.error is fine for debugging, but we don't want it in the finished code

async fetchData(input) {
const queryObject = {
input,
auth_key: process.env.BOT_AUTH_KEY,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having process.env.BOT_AUTH_KEY in the react-code means that we are exposing our API-Key to the whole world, which we really don't want. This must be moved to proxyview.py instead. The same is true for freva_config.

return (
<div>
<Card className="mb-3 shadow-sm">
<div className="btn btn-outline-secondary border-0 p-3 rounded-top text-start card-header shadow-sm button-div">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The outer div doesn't seem necessary, you can replace it by a simple <>
  • Don't use btn here. Due to the btn the user experience here implies that the Header of the section/Card is clickable, which it isn't. A simple <Card.Header> is fine as well
  • Likewise, <Card.Body> looks fine for me, too. It isn't collapsible.
  • Maybe add some vertical margins between the links. It is visually a bit hard to distinguish between the different links.
  • What's the point of hiding the link to the thread behind a onClick? I think a <a className="text-wrap" href={`/chatbot/?thread_id=${element.thread}`} > would be totally fine. It would be even better, if you could use <Link to... instead (from import { Link } from "react-router";) because it wouldn't cause the whole website to reload and the user would stay inside react, but this would need more adjustments.

export const frevaGPTReducer = (state = frevaGPTInitialState, action) => {
switch (action.type) {
case constants.SET_CONVERSATION:
return { ...state, conversation: action.payload };
Copy link
Collaborator

@Karinon Karinon Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retrieving old threads leads to unescaped URI-Fragments in the User-Variants of the thread, which looks quite ugly (Name%20the%20most%20important%20parameters%20in%20meteorology).

I would blame the backend for this, but I am not entirely sure as I have never seen the backend. We could try to solve it here, e.g. with something like this:

    case constants.SET_CONVERSATION: {
      const payload = action.payload;
      for (const payloadElem of payload) {
        if (payloadElem.variant === "User") {
          payloadElem.content = decodeURIComponent(payloadElem.content);
        }
      }
      return { ...state, conversation: payload };
    }

Take it with a grain of salt, as I don't have a complete overview over possible edge-cases

this.setState({ userInput: "" });
}

async handleSubmit(input) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be reasonable to prevent empty strings to be passed to the backend

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adapted the affected functions. But edge cases like submitting a whitespace or similar absurd inputs are not covered. Should we thin kabout a more comprehensive approach? The Bot seems to handle this kind of inputs well (at least the one I tested).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take on this would be something like this as the first action in handleSubmit (since this is the function which performs the actual action either way)

    if (!input.trim()) {
      return;
    }

Or instead of negating the value (empty strings are falsy) you could as well surround it with isEmpty. By this we would at least cover whitespaces.

@@ -3,13 +3,22 @@ const { merge } = require("webpack-merge");
const baseConfig = require("./webpack/webpack.base.config");
const devConfig = require("./webpack/webpack.dev.config");
const prodConfig = require("./webpack/webpack.prod.config");
const webpack = require('webpack');

const Dotenv = require("dotenv-webpack");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you moved dotenv to the base, we can delete it here.

@@ -282,6 +299,15 @@ def _set_favicon(html_color: str, project_root: Path) -> None:
]
MENU_ENTRIES = []

CHAT_BOT_URL = "http://vader5-icpub.lvt.dkrz.de:8502"
CHAT_BOT_AUTH_KEY = os.environ.get("CHAT_BOT_AUTH_KEY")
CHAT_BOT_FREVA_CONFIG = os.environ.get("CHAT_BOT_AUTH_KEY")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple things here:

  • You renamed BOT_AUTH_KEY to CHAT_BOT_AUTH_KEY. This is not an issue per se but I stumbled upon this because I was not aware of the change and I am not sure if this was by accident. We can keep it though, I don't mind.
  • Copy-Paste error in the env-var of CHAT_BOT_FREVA_CONFIG, but:
  • I assume that CHAT_BOT_FREVA_CONFIG is not needed, as the web has already a env-var for this: EVALUATION_SYSTEM_CONFIG_FILE (I should have mentioned it earlier, but I forgot, sorry). However, it isn't stored in a variable, as far as I can see. So you can rename the var CHAT_BOT_FREVA_CONFIG to EVALUATION_SYSTEM_CONFIG_FILE, I guess. One thing to node is, however, that in dev-envs EVALUATION_SYSTEM_CONFIG_FILE will obviously refer to a file on the local file system where the (currently) globally deployed chatbot has no access to. I have also no idea what the bot does with this information. I have briefly tested it and it seems that it still works

@@ -0,0 +1 @@

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete the file

placeholder="Ask a question"
/>
{this.state.loading ? (
<Button variant="outline-danger" onClick={this.handleStop}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nitpicky, but: Could you add className="d-flex align-items-center" to both buttons which contain the icon?

The icons are not vertically centered out of the box and with this classes they look so much nicer.

return pingSuccessful;
};

const getBotModels = async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error-handling. Do it by checking either the status code or response.ok.

upstream_response = requests.get(base_url[:-1], params=params, stream=True)
upstream_response.raise_for_status()
except requests.RequestException as e:
return StreamingHttpResponse(
Copy link
Collaborator

@Karinon Karinon Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I was dealing with broken BOT_AUTH_KEYs, I had the pleasure to meet this Exception here and I think we need a few adjustments:

  • replace 502 by the actual status_code. It seems that it is stored in e.response.status_code
  • Maybe we also don't want to expose the actual error message, because it contains the URL to the bot-server which we don't want to expose (and probably it is not even available from the outside). We should log the actual error message but we only expose something generic. I have something like this in mind:
            logger = logging.getLogger("freva-web")
            logger.exception(e)
            status_code = e.response.status_code
            error_message = HTTPStatus(status_code).phrase

HTTPStatus comes from from http import HTTPStatus

  • I think it would make things easier if we would deal with a JSON instead of a string. My initial idea would be to replace the error response by something like
            return StreamingHttpResponse(
                json.dumps(
                    {
                        "variant": "ServerError",
                        "content": f"Error while connecting to the external API: {error_message}",
                    }
                ),
                content_type="application/json",
                status=status_code,
            )

But I am not sure. In other parts of the django backend we simply create errors like {"error": "error message"}, but if it helps you to keep the structure of the rest, feel free to alter it. Keep in mind that this error message will also appear for other bot-requests like availablebots

};

// response of a new bot request is streamed
const response = await fetch(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When having an error case (backend returns some kind of Server Error), the user will not see any output. I think before going into reading the stream, you have to check response.ok and respond accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants