Skip to content

Commit

Permalink
Add user message to responses
Browse files Browse the repository at this point in the history
  • Loading branch information
jlowin committed Jan 14, 2024
1 parent 673c574 commit 3933346
Show file tree
Hide file tree
Showing 10 changed files with 52 additions and 50 deletions.
44 changes: 23 additions & 21 deletions docs/ai/interactive/assistants.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,26 @@ Each assistant can be given a list of `tools` that it can use when responding to

OpenAI provides a small number of built-in tools for assistants. The most useful is the "code interpreter", which lets the assistant write and execute Python code. To use the code interpreter, add it to your assistant's list of tools:

```python
from marvin.beta import Assistant
from marvin.beta.assistants import pprint_messages, CodeInterpreter
!!! example "Using the code interpreter"

ai = Assistant(name='Marvin', tools=[CodeInterpreter])
response = ai.say('write and test a function that returns 2 + 2')
Assistants can not browse the web by default. We can add this capability by giving them a tool that takes a URL and returns the HTML of that page. This assistant uses that tool as well as the code interpreter to count how many titles on Hacker News mention AI:

```python
from marvin.beta import Assistant
from marvin.beta.assistants import pprint_messages, CodeInterpreter

ai = Assistant(name='Marvin', tools=[CodeInterpreter])
response = ai.say(
"Write and test a function that multiplies two numbers. "
"Show the code and your tests."
)

# pretty-print the response
pprint_messages(response)
```
!!! success "Result"
![](/assets/images/ai/assistants/code_interpreter.png)

# pretty-print the response
pprint_messages(response)
```

#### Custom tools

Expand All @@ -89,14 +99,10 @@ A major advantage of using Marvin's assistants API is that you can add your own

!!! example "Using custom tools"

Assistants can not browse the web by default. We can add this capability by giving them a tool that takes a URL and returns the HTML of that page. This assistant uses that tool as well as the code interpreter to count how many titles on Hacker News mention AI:
Assistants can not browse the web by default. We can add this capability by giving them a tool that takes a URL and returns the HTML of that page. This assistant uses that tool to count how many titles on Hacker News mention AI:

```python
from marvin.beta.assistants import (
Assistant,
CodeInterpreter,
pprint_messages
)
from marvin.beta.assistants import Assistant, pprint_messages
import requests


Expand All @@ -107,18 +113,14 @@ A major advantage of using Marvin's assistants API is that you can add your own


# Integrate custom tools with the assistant
ai = Assistant(name="Marvin", tools=[CodeInterpreter, visit_url])

# Give the assistant an objective
response = ai.say(
"Go to Hacker News and compute how many titles mention AI"
)
ai = Assistant(name="Marvin", tools=[visit_url])
response = ai.say("Count how many HN front page titles mention LLMs")

# pretty-print the response
pprint_messages(response)
```
!!! success "Result"
![](/assets/images/ai/assistants/using_tools.png)
![](/assets/images/ai/assistants/custom_tools.png)

### Talking to an assistant

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/ai/assistants/custom_tools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/assets/images/ai/assistants/quickstart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/assets/images/ai/assistants/using_tools.png
Binary file not shown.
29 changes: 14 additions & 15 deletions src/marvin/beta/assistants/assistants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
run_sync,
)
from marvin.utilities.logging import get_logger
from marvin.utilities.openai import get_client
from marvin.utilities.openai import get_openai_client

from .threads import Thread

Expand Down Expand Up @@ -69,23 +69,22 @@ async def say_async(
"""
thread = thread or self.default_thread

last_message = await thread.get_messages_async(limit=1)
if last_message:
last_msg_id = last_message[0].id
else:
last_msg_id = None

# post the message
if message:
api_message = await thread.add_async(message, file_paths=file_paths)
msg_id = api_message.id

# if no message, get the ID of the most recent message
else:
msgs = await thread.get_messages_async(limit=1)
if msgs:
msg_id = msgs[0].id
else:
msg_id = None
await thread.add_async(message, file_paths=file_paths)

# run the thread
async with self:
await thread.run_async(assistant=self, **run_kwargs)

response_messages = await thread.get_messages_async(after_message=msg_id)
# load all messages, including the user message
response_messages = await thread.get_messages_async(after_message=last_msg_id)
return response_messages

def __enter__(self):
Expand Down Expand Up @@ -114,7 +113,7 @@ async def create_async(self):
raise ValueError(
"Assistant has an ID and has already been created in the OpenAI API."
)
client = get_client()
client = get_openai_client()
response = await client.beta.assistants.create(
**self.model_dump(
include={"name", "model", "metadata", "file_ids", "metadata"}
Expand All @@ -128,7 +127,7 @@ async def create_async(self):
async def delete_async(self):
if not self.id:
raise ValueError("Assistant has no ID and doesn't exist in the OpenAI API.")
client = get_client()
client = get_openai_client()
await client.beta.assistants.delete(assistant_id=self.id)
self.id = None

Expand All @@ -138,7 +137,7 @@ def load(cls, assistant_id: str, **kwargs):

@classmethod
async def load_async(cls, assistant_id: str, **kwargs):
client = get_client()
client = get_openai_client()
response = await client.beta.assistants.retrieve(assistant_id=assistant_id)
return cls(**(response.model_dump() | kwargs))

Expand Down
12 changes: 6 additions & 6 deletions src/marvin/beta/assistants/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from marvin.types import Tool
from marvin.utilities.asyncio import ExposeSyncMethodsMixin, expose_sync_method
from marvin.utilities.logging import get_logger
from marvin.utilities.openai import get_client
from marvin.utilities.openai import get_openai_client

from .assistants import Assistant
from .threads import Thread
Expand Down Expand Up @@ -54,20 +54,20 @@ def format_tools(cls, tools: Union[None, list[Union[Tool, Callable]]]):

@expose_sync_method("refresh")
async def refresh_async(self):
client = get_client()
client = get_openai_client()
self.run = await client.beta.threads.runs.retrieve(
run_id=self.run.id, thread_id=self.thread.id
)

@expose_sync_method("cancel")
async def cancel_async(self):
client = get_client()
client = get_openai_client()
await client.beta.threads.runs.cancel(
run_id=self.run.id, thread_id=self.thread.id
)

async def _handle_step_requires_action(self):
client = get_client()
client = get_openai_client()
if self.run.status != "requires_action":
return
if self.run.required_action.type == "submit_tool_outputs":
Expand Down Expand Up @@ -117,7 +117,7 @@ def get_tools(self) -> list[AssistantTool]:
return tools

async def run_async(self) -> "Run":
client = get_client()
client = get_openai_client()

create_kwargs = {}

Expand Down Expand Up @@ -196,7 +196,7 @@ async def refresh_run_steps_async(self):
max_attempts = max_fetched / limit + 2

# Fetch the latest run steps
client = get_client()
client = get_openai_client()

response = await client.beta.threads.runs.steps.list(
run_id=self.run.id,
Expand Down
10 changes: 5 additions & 5 deletions src/marvin/beta/assistants/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
run_sync,
)
from marvin.utilities.logging import get_logger
from marvin.utilities.openai import get_client
from marvin.utilities.openai import get_openai_client
from marvin.utilities.pydantic import parse_as

logger = get_logger("Threads")
Expand Down Expand Up @@ -50,7 +50,7 @@ async def create_async(self, messages: list[str] = None):
raise ValueError("Thread has already been created.")
if messages is not None:
messages = [{"role": "user", "content": message} for message in messages]
client = get_client()
client = get_openai_client()
response = await client.beta.threads.create(messages=messages)
self.id = response.id
return self
Expand All @@ -62,7 +62,7 @@ async def add_async(
"""
Add a user message to the thread.
"""
client = get_client()
client = get_openai_client()

if self.id is None:
await self.create_async()
Expand Down Expand Up @@ -90,7 +90,7 @@ async def get_messages_async(
) -> list[Union[ThreadMessage, dict]]:
if self.id is None:
await self.create_async()
client = get_client()
client = get_openai_client()

response = await client.beta.threads.messages.list(
thread_id=self.id,
Expand All @@ -108,7 +108,7 @@ async def get_messages_async(

@expose_sync_method("delete")
async def delete_async(self):
client = get_client()
client = get_openai_client()
await client.beta.threads.delete(thread_id=self.id)
self.id = None

Expand Down
4 changes: 2 additions & 2 deletions src/marvin/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rich.console import Console
from typing import Optional
from marvin.utilities.asyncio import run_sync
from marvin.utilities.openai import get_client
from marvin.utilities.openai import get_openai_client
from marvin.cli.version import display_version

app = typer.Typer()
Expand All @@ -27,7 +27,7 @@ def main(


async def process_stdin(model: str, max_tokens: int):
client = get_client()
client = get_openai_client()
content = sys.stdin.read()
last_chunk_ended_with_space = False

Expand Down
3 changes: 2 additions & 1 deletion src/marvin/utilities/openai.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Module for working with OpenAI."""

import asyncio
from functools import lru_cache
from typing import Optional

from openai import AsyncClient


def get_client() -> AsyncClient:
def get_openai_client() -> AsyncClient:
"""
Retrieves an OpenAI client with the given api key and organization.
Expand Down

0 comments on commit 3933346

Please sign in to comment.