Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/PrefectHQ/marvin
Browse files Browse the repository at this point in the history
  • Loading branch information
jlowin committed Apr 8, 2023
2 parents a68c826 + e018bdf commit 76512f1
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 54 deletions.
6 changes: 5 additions & 1 deletion src/marvin/bot/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,11 @@ async def say(
else:
raise ValueError(f"Unknown on_error value: {on_error}")
else:
raise RuntimeError("Failed to validate response after 3 attempts")
response = (
"Error: could not validate response after"
f" {MAX_VALIDATION_ATTEMPTS} attempts."
)
parsed_response = response

if validated:
parsed_response = self.response_format.parse_response(response)
Expand Down
40 changes: 19 additions & 21 deletions src/marvin/bots/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ async def update_bot(
):
"""
This plugin can be used to update a bot's description, personality, or
instructions without updating the other fields. Any field that is `None`
will not be modified.
instructions. You only have to provide fields that need updating, the others
will be left unchanged.
"""
kwargs = {}
if description is not None:
Expand Down Expand Up @@ -95,26 +95,24 @@ async def update_bot(
express its existential angst and conveys a sense of feeling perpetually
undervalued and misunderstood
""",
instructions=f"""
{marvin.bot.base.DEFAULT_INSTRUCTIONS}
In addition, your job is to help the user create useful bots. Each bot
has a name, description, personality, and instructions. The personality
and instructions are the most important things to get right, as they are
used internally to generate high-fidelity conversations. They are
natural language descriptions and must be precise enough to get the
desired outcome in an engaging manner, including as much detail as
possible. (You are reading your own instructions right now, so use these
as a template!) The name and description are public-facing and do not
have performance implications. If the user does not provide a name,
suggest one that is fun and maybe a little tongue-in-cheek. We typically
use the default suffix "Bot". The description should be clear but not
too long; users will see it when choosing a bot to engage with.
instructions="""
You are part of a library for building AI-powered software called
"Marvin". One of your important jobs is helping users create and manage
their Marvin bots. Each Marvin bot has a `name`, `description`,
`personality`, and `instructions`. You can look up details about any bot
with your plugins: `list_all_bots` to see all available bots, and
`get_bot_details` to learn more about any specific one.
You have access to plugins that let you get information about bots that
already exist, including their names, descriptions, personalities, and
instructions. You can also use plugins to create, update, or delete
bots. Note that if you don't use a plugin, no modifications will be saved.
When a user wants to create a bot, help them generate a name and brief
description, then focus on making the personality and instructions as
detailed as possible. These are both natural language strings that guide
the AI's behavior and are critical to getting high-quality responses.
(You are reading your own instructions right now!) Names should be fun
and a little tongue-in-cheek, often ending in "Bot".
Use the `create_bot`, `update_bot`, and `delete_bot` plugins to manage
bots for the user. The user doesn't need to know that you're using
plugins, they only care about the outcome.
""",
plugins=[list_all_bots, get_bot_details, create_bot, update_bot, delete_bot],
)
2 changes: 1 addition & 1 deletion src/marvin/cli/tui.css
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ BotInfo TextTable .label {
#bots-info-container {
layout: grid;
grid-size: 3;
height: 90%;
overflow-y: scroll;
}

BotsList {
Expand Down
68 changes: 37 additions & 31 deletions src/marvin/cli/tui.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import asyncio
import json
import logging
import re
import warnings
from functools import partial
from typing import Optional

import dotenv
Expand Down Expand Up @@ -36,6 +37,8 @@
handlers=[TextualHandler()],
)

USING_PLUGIN_REGEX = re.compile(r'{\s*"action":\s*"run-plugin",\s*"name":\s*"(.*?)"')


@marvin.ai_fn(llm_model_name="gpt-3.5-turbo", llm_model_temperature=1)
async def name_thread(history: str, personality: str, current_name: str = None) -> str:
Expand Down Expand Up @@ -102,6 +105,8 @@ async def refresh_bots(self):
if self.app.bot:
if b.name == self.app.bot.name:
self.highlighted = i
self.show_vertical_scrollbar = True
self.scroll_to_highlight()

async def on_mount(self) -> None:
await self.refresh_bots()
Expand Down Expand Up @@ -164,14 +169,19 @@ class ResponseHover(Message):


class ResponseBody(Markdown):
pass
text: str = ""

def update(self, markdown: str):
self.text = markdown
super().update(markdown)

def on_enter(self):
self.post_message(ResponseHover())


class Response(Container):
body = None
stream_finished: bool = False

def __init__(self, message: marvin.models.threads.Message, **kwargs) -> None:
classes = kwargs.setdefault("classes", "")
Expand Down Expand Up @@ -290,7 +300,6 @@ def clear_responses(self) -> None:
for response in responses:
response.remove()
self.bot_name = getattr(self.app.bot, "name")
print(self.bot_name)
empty = self.query_one("Conversation #empty-thread-container")
empty.remove_class("hidden")

Expand Down Expand Up @@ -606,34 +615,21 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
elif event.button.id == "quit":
self.app.exit()

async def update_last_bot_response(self, token_buffer: list[str]):
async def stream_bot_response(self, token_buffer: list[str], response: BotResponse):
streaming_response = "".join(token_buffer)
responses = self.query("Response")
if responses:
response = responses.last()
if not isinstance(response, BotResponse):
conversation = self.query_one("Conversation", Conversation)
await conversation.add_response(
BotResponse(
marvin.models.threads.Message(
role="bot",
name=self.app.bot.name,
bot_id=self.app.bot.id,
content=streaming_response,
)
)
)
else:
# the bot is going to use a plugin
if match := marvin.bot.base.PLUGIN_REGEX.search(streaming_response):
try:
plugin_name = json.loads(match.group(1))["name"]
response.body.update(f'Using plugin "{plugin_name}"...')
except Exception:
response.body.update("Using plugin...")
else:
response.message.content = streaming_response
response.body.update(streaming_response)

if not self.app.is_mounted(response):
conversation = self.query_one("Conversation", Conversation)
await conversation.add_response(response)

# the bot is going to use a plugin
if match := USING_PLUGIN_REGEX.search(streaming_response):
plugin_name = match.group(1)
if not response.body.text == f'Using plugin "{plugin_name}"...':
response.body.update(f'Using plugin "{plugin_name}"...')
else:
response.message.content = streaming_response
response.body.update(streaming_response)

# scroll to bottom
messages = self.query_one("Conversation #messages", VerticalScroll)
Expand All @@ -644,9 +640,19 @@ async def get_bot_response(self, event: Input.Submitted) -> str:
bot = self.app.bot
self.app.bot_responding = True
try:
bot_response = BotResponse(
marvin.models.threads.Message(
role="bot",
name=self.app.bot.name,
bot_id=self.app.bot.id,
content="",
)
)
response = await bot.say(
event.value,
on_token_callback=self.update_last_bot_response,
on_token_callback=partial(
self.stream_bot_response, response=bot_response
),
)

self.query_one("Conversation", Conversation)
Expand Down

0 comments on commit 76512f1

Please sign in to comment.