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

Feat/continue generate #361

Closed
wants to merge 6 commits into from
Closed

Conversation

satoxiw
Copy link
Contributor

@satoxiw satoxiw commented Jun 12, 2024

Issue #, if available:

Description of changes:

  • テキストファイルのドラッグおよびのファイル添付指定
    • ただし、指定されたテキストファイルはマークダウン書式のコードブロックとしてプロンプトに挿入される動きとなる
  • ユーザ入力の出力をReact.FragmentからChatMessageMarkdownに変更
  • "続きを生成"機能を追加
    • stop_reasonmax_tokensだった場合、続きを生成ボタンを出すように修正

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link
Contributor

@statefb statefb left a comment

Choose a reason for hiding this comment

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

CIに失敗しているので、そちらも合わせてご確認お願いします!(方針が確定してからで大丈夫です!)

conversation.message_map[parent_id].children.append(message_id) # type: ignore

# TODO: Continueが空メッセージという仕様で良いか要検討
# Empty messages for continuity purposes are not added.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • 空の判定はddbから読み取ったshould_continueから判別可能だと思ったのですが、ここで敢えて空の判定を挟んでいる理由を教えてください(打ち合わせ時に教えていただいたと思うのですが、忘れてしまいました。申し訳ないのですが、ここに記録残していただけると助かります。またそれをコード中のコメントで英語で記載しておいていただけると助かります)

仮に必要な場合は、bedrock claude chatは空メッセージをフロントから受け付けない仕様なので、空の判定で良いと思います!

  • 空であることの判定は、websocket.pyに記載いただいているものと同様のもので良いと思いましたが、ここでtextかどうかも判定している理由はなんですか?
  • new_messageの直前に、continueボタンによる分岐である旨をコメントで記載お願いします!

Copy link
Contributor Author

@satoxiw satoxiw Jun 18, 2024

Choose a reason for hiding this comment

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

空の判定はddbから読み取ったshould_continueから判別可能だと思ったのですが、ここで敢えて空の判定を挟んでいる理由を教えてください(打ち合わせ時に教えていただいたと思うのですが、忘れてしまいました。申し訳ないのですが、ここに記録残していただけると助かります。またそれをコード中のコメントで英語で記載しておいていただけると助かります)

仮にmax_tokensとなって続きがあったとしても、それを無視して別のリクエストを送るケースもあるかもという想定からです。should_continueに頼ってしまうとユーザの意図した動作とならない可能性があります。

仮に必要な場合は、bedrock claude chatは空メッセージをフロントから受け付けない仕様なので、空の判定で良いと思います!

了解です。

空であることの判定は、websocket.pyに記載いただいているものと同様のもので良いと思いましたが、ここでtextかどうかも判定している理由はなんですか?

単にtextでない場合の仕様をしっかり読み解けておらず、imageの場合にbodyが空の場合があるのかな?という想定からです。content_tyepが"image"の場合もbodyが空という状況はない感じでしょうか?

new_messageの直前に、continueボタンによる分岐である旨をコメントで記載お願いします!

分岐の直前にコメントで入れておきます。

Copy link
Contributor

@statefb statefb Jun 19, 2024

Choose a reason for hiding this comment

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

仮にmax_tokensとなって続きがあったとしても、それを無視して別のリクエストを送るケースもあるかもという想定からです。should_continueに頼ってしまうとユーザの意図した動作とならない可能性があります。

ありがとうございます、理解しました!

単にtextでない場合の仕様をしっかり読み解けておらず、imageの場合にbodyが空の場合があるのかな?という想定からです。content_tyepが"image"の場合もbodyが空という状況はない感じでしょうか?

別途slackでの議論の通りなのですが、空文字に意味を持たせたくないので、項目追加でお願いします!下記のようにChatInput (BE)と、PostMessageRequest (FE) に追加すればOKだと思います!

class ChatInput(BaseSchema):
    conversation_id: str
    message: MessageInput
    bot_id: str | None = Field(None)
+    continue_generate: bool
export type PostMessageRequest = {
  conversationId?: string;
  message: MessageContent & {
    parentMessageId: null | string;
  };
  botId?: string;
+  continueGenerate: bool
};

if chat_input.message.content[0].body != "":
messages.append(chat_input.message) # type: ignore
else:
if messages[-1].role == "assistant":
Copy link
Contributor

Choose a reason for hiding this comment

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

  • assisntatかどうかの判別をあえて入れている理由は何でしょうか?(空メッセージでない = 続きが必要 = 自動的に最後のメッセージはassistant)
  • 関数の責務を考慮すると、こちらではなくprepare_conversationで処理したほうが可読性が高いと思うので、strip処理はそちらに記載お願いします!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assisntatかどうかの判別をあえて入れている理由は何でしょうか?(空メッセージでない = 続きが必要 = 自動的に最後のメッセージはassistant)

ここでtrim処理をしなければならないメッセージは必ずassistantの継続メッセージのみとなるため、安全のため(なんらかの仕様で空メッセージが入ってきた場合を考慮して)入れているだけなので、不要かもですね。

関数の責務を考慮すると、こちらではなくprepare_conversationで処理したほうが可読性が高いと思うので、strip処理はそちらに記載お願いします!

となると、前段で行なっているtrace_to_root処理をprepare_conversationで行うことになり冗長な動きになるか、messagesprepare_conversationで作るとした場合、process_chat_inputで行なっている他の処理とのからみもあり大改修になりそうですが、いかがしましょう。

Copy link
Contributor

@statefb statefb Jun 19, 2024

Choose a reason for hiding this comment

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

大改修になりそうですが、いかがしましょう。

すみません、確かにtrace_to_root後に適用する必要がありますね。ご指摘ありがとうございます!
意図としては、この部分で細かな前処理をなるべく避けたい、というのが意図になります。例えば保存する前にtrimするというのはいかがでしょうか(問題なく出力できるかの簡単な検証は必要だと思いますが)。
ストリーム処理の場合、backend.app.stream.pyAnthropicStreamHandlerBedrockStreamHandlerの該当箇所を下記のように変更することで、保存前にtrimが可能です。

AnthropicStreamHandlerの場合:

class AnthropicStreamHandler(BaseStreamHandler):
    """Stream handler for Anthropic models."""

    def run(self, args: dict):
       ...
            if isinstance(event, ContentBlockDeltaEvent):
                ...
            elif isinstance(event, MessageDeltaEvent):
                ...
            elif isinstance(event, MessageStopEvent):
                concatenated = "".join(completions)
                metrics = event.model_dump()["amazon-bedrock-invocationMetrics"]
                input_token_count = metrics.get("inputTokenCount")
                output_token_count = metrics.get("outputTokenCount")
                price = calculate_price(
                    self.model, input_token_count, output_token_count
                )
                response = self.on_stop(
                    OnStopInput(
+                        full_token=concatenated.strip(),
                        stop_reason=stop_reason,
                        input_token_count=input_token_count,
                        output_token_count=output_token_count,
                        price=price,
                    )
                )
                yield response
            else:
                continue

非ストリーム処理で利用されるbackend.app.usecases.chat.pychat()では、reply_txtstripする方針が綺麗にシンプルに実装できるかなと思いましたが、いかがでしょうか?

@@ -181,7 +181,18 @@ def process_chat_input(
node_id=chat_input.message.parent_message_id,
message_map=message_map,
)
messages.append(chat_input.message) # type: ignore

continueGenerate = False
Copy link
Contributor

Choose a reason for hiding this comment

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

仮にddbに永続化されたものだけで判定が不可能である場合、この変数(continueGenerate)はprepare_conversationで返したほうが良いかと思いました(タプルに追加する)。
また細かいですが、should_continueではなくあえて変数名を変えた理由は何でしょうか?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

仮にddbに永続化されたものだけで判定が不可能である場合、この変数(continueGenerate)はprepare_conversationで返したほうが良いかと思いました(タプルに追加する)。

これは↑の改修とすこし絡みそうなので相談させてください

また細かいですが、should_continueではなくあえて変数名を変えた理由は何でしょうか?

単に修正漏れです。こちらの変数をshould_continue作成前に作っていたためです。名前合わせておきます。

Copy link
Contributor

Choose a reason for hiding this comment

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

上記のコメント(continue_generateをinputに追加・保存前にtrim)を反映した場合、下記のようにシンプルになるかと思いましたが、いかがでしょうか?

if not chat_input.continue_generate:
  messages.append(chat_input.message)

args = compose_args(
......

def on_stop(arg: OnStopInput, **kwargs) -> None:
        if chat_input.continue_generate:
          # For continue generate
            conversation.message_map[conversation.last_message_id].content[
                0
            ].body += arg.full_token
         else:
           ......

Copy link
Contributor Author

Choose a reason for hiding this comment

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

こちら、continue_generate導入に伴い上記の用に修正できるので対応します!

backend/app/websocket.py Outdated Show resolved Hide resolved
backend/app/websocket.py Outdated Show resolved Hide resolved
@statefb
Copy link
Contributor

statefb commented Jun 13, 2024

duplicated PR: #280

Copy link
Contributor

@wadabee wadabee left a comment

Choose a reason for hiding this comment

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

ご対応ありがとうございます!
コメントの内容について、修正をお願いしますmm
もし対応が大変な場合は、お手伝いしますので、ご連絡ください。

Comment on lines +160 to +166
// [Customize]インプットメッセージもMarkdown書式で整形表示できるよう修正
<ChatMessageMarkdown
key={idx}
messageId={String(idx)}>
{content.body}
</ChatMessageMarkdown>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

自身の入力データはRawな状態で表示しておきたいです。
LLMのプロンプトはある種のコマンドだと思うので加工はせずに、入力した内容をそのまま表示させておきたいという意図です。
マークダウンとRawな入力値を切り替え表示できるとベターな気がしますが、一旦このPRでは対応なしにしておきたいです

Copy link
Contributor

Choose a reason for hiding this comment

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

ファイルアップロード時に表示する関係でマークダウンにしたと思うんですが、ファイルの中身は基本的に非表示として、クリックしたら中身が見れるようなUXにしたいです(コードを読み進めて気づきました)
利用者としては、画面上でファイルをアップロードしたという事実だけがわかれば良くて、ファイルの中身は追加アクションで確認できる方がUXが良いかなと思っています(長いファイルだと、ファイルの中身が表示されて画面が占有されてしまうため)

Comment on lines +12 to +25
.file-name {
text-align: center;
max-width: 8rem; /* ファイル名の幅 */
white-space: pre-wrap; /* 折り返しを有効にする */
word-wrap: break-word; /* 長い単語の途中で折り返す */
}

.code-container {
padding: 3px;
}
.code-header {
margin-left: 18px;
margin-top: 5px;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

スタイルの設定は、tailwind CSSで行なってください。

const sendContent = useCallback(() => {
const filesString = textFiles.length > 0 ? textFiles.map(file => `\`\`\`${getFileLanguage(file.name)}:${file.name}\n${file.content}\n\`\`\`\n\n`).join('') : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

claudeはXMLタグに最適化されていますが、マークダウンでも精度的には問題ないでしょうか?

const inputRef = useRef<HTMLDivElement>(null);

const truncateFileName = (name: string, maxLength = 30) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

関数はuseCallbackの利用をお願いしますmm

const { disabledImageUpload, model, acceptMediaType } = useModel();

const extendedAcceptMediaType = useMemo(() => {
return [...acceptMediaType, '.md', '.ts', '.js']; // 追加のメディアタイプをここに追加
Copy link
Contributor

Choose a reason for hiding this comment

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

利用できる拡張子は、hooksかconstantsで管理したいです。
extensionToLanguageMapも同様です。

Comment on lines +422 to +423
<PiFileTextThin className="h-16 w-16 text-gray-500" />
<div className="file-name">{truncateFileName(file.name)}</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

少し細かいですが、アイコンの大きさはw-12 h-12、文字サイズtext-sm、アイコンと文字の色:text-grayでお願いします。

))}
</div>
)}
{(getShouldContinue() || messages.length > 1) && (
Copy link
Contributor

Choose a reason for hiding this comment

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

Reactの慣習として、テンプレート部分で関数を実行しないので、修正をお願いします。
hooks側で関数を実行して、その実行結果をStateに設定する方法をよく使います。

@statefb statefb marked this pull request as draft June 25, 2024 11:21
@statefb
Copy link
Contributor

statefb commented Jun 25, 2024

PR分割につき一旦draftにさせてください! (こちらはmergeせずcloseする予定です)

@satoxiw
Copy link
Contributor Author

satoxiw commented Jun 28, 2024

こちら、#401#412 に分けましたので、こちらはCloseします。

@satoxiw satoxiw closed this Jun 28, 2024
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