Add tool messages to arcade chat conversation history (#49)

* Adds tool calls and tool responses to the conversation history for
streaming and non-streaming.
* Adds debug flag for arcade chat which displays the tool calls and tool
responses that occurred during generate `arcade chat -d`
This commit is contained in:
Eric Gustin 2024-09-21 10:26:20 -07:00 committed by GitHub
parent 399ad5f878
commit 18cc192290
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 49 additions and 9 deletions

View file

@ -21,6 +21,8 @@ from arcade.cli.utils import (
create_cli_catalog,
display_eval_results,
display_streamed_markdown,
display_tool_messages,
get_tool_messages,
markdownify_urls,
validate_and_get_config,
)
@ -165,6 +167,7 @@ def chat(
"--no-tls",
help="Whether to disable TLS for the connection to the Arcade Engine.",
),
debug: bool = typer.Option(False, "--debug", "-d", help="Show debug information"),
) -> None:
"""
Chat with a language model.
@ -185,7 +188,7 @@ def chat(
try:
# start messages conversation
messages: list[dict[str, Any]] = []
history: list[dict[str, Any]] = []
chat_header = Text.assemble(
"\n",
@ -217,30 +220,39 @@ def chat(
# Add the input to history
readline.add_history(user_input)
messages.append({"role": "user", "content": user_input})
history.append({"role": "user", "content": user_input})
tool_messages: list[dict] = []
if stream:
# TODO Fix this in the client so users don't deal with these
# typing issues
stream_response = client.chat.completions.create( # type: ignore[call-overload]
model=model,
messages=messages,
messages=history,
tool_choice="generate",
user=user_email,
stream=True,
)
role, message_content = display_streamed_markdown(stream_response, model)
role, message_content, tool_messages = display_streamed_markdown(
stream_response, model
)
history += tool_messages
else:
response = client.chat.completions.create( # type: ignore[call-overload]
model=model,
messages=messages,
messages=history,
tool_choice="generate",
user=user_email,
stream=False,
)
message_content = response.choices[0].message.content or ""
role = response.choices[0].message.role
tool_messages = get_tool_messages(response.choices[0])
history += tool_messages
role = response.choices[0].message.role
if role == "assistant":
message_content = markdownify_urls(message_content)
console.print(
@ -249,7 +261,10 @@ def chat(
else:
console.print(f"\n[bold magenta]{role}:[/bold magenta] {message_content}")
messages.append({"role": role, "content": message_content})
if debug:
display_tool_messages(tool_messages)
history.append({"role": role, "content": message_content})
except KeyboardInterrupt:
console.print("Chat stopped by user.", style="bold blue")

View file

@ -55,13 +55,35 @@ def create_cli_catalog(
return catalog
def display_streamed_markdown(stream: Stream[ChatCompletionChunk], model: str) -> tuple[str, str]:
def display_tool_messages(tool_messages: list[dict]) -> None:
for message in tool_messages:
if message["role"] == "assistant":
for tool_call in message.get("tool_calls", []):
console.print(
f"[bright_black]Called tool '{tool_call['function']['name']}' with parameters: {tool_call['function']['arguments']}[/bright_black]"
)
elif message["role"] == "tool":
console.print(
f"[bright_black]Tool '{message['name']}' returned: {message['content']}[/bright_black]"
)
def get_tool_messages(choice: dict) -> list[dict]:
if hasattr(choice, "tool_messages") and choice.tool_messages:
return choice.tool_messages # type: ignore[no-any-return]
return []
def display_streamed_markdown(
stream: Stream[ChatCompletionChunk], model: str
) -> tuple[str, str, list]:
"""
Display the streamed markdown chunks as a single line.
"""
from rich.live import Live
full_message = ""
tool_messages = []
role = ""
with Live(console=console, refresh_per_second=10) as live:
for chunk in stream:
@ -76,12 +98,15 @@ def display_streamed_markdown(stream: Stream[ChatCompletionChunk], model: str) -
markdown_chunk = Markdown(full_message)
live.update(markdown_chunk)
# Display and get tool messages if they exist
tool_messages += get_tool_messages(choice) # type: ignore[arg-type]
# Markdownify URLs in the final message if applicable
if role == "assistant":
full_message = markdownify_urls(full_message)
live.update(Markdown(full_message))
return role, full_message
return role, full_message, tool_messages
def markdownify_urls(message: str) -> str: