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

ui.Chat() now correctly handles new ollama.chat() return value introduced in ollama 0.4 #1787

Merged
merged 8 commits into from
Nov 26, 2024

Conversation

cpsievert
Copy link
Collaborator

Ollama 0.4 changed the return type of ollama.chat() (ChatResponse) from a TypedDict to a pydantic.BaseModel. As a result, passing that return value directly to .append_message() or .append_message_stream() doesn't work:

This PR fixes the issue, adds a unit test, and fixes another failing unit test.

# ------------------------------------------------------------------------------------
# A basic Shiny Chat example powered by Ollama.
# To run it, you'll need an Ollama server running locally.
# To download and run the server, see https://github.com/ollama/ollama
# To install the Ollama Python client, see https://github.com/ollama/ollama-python
# ------------------------------------------------------------------------------------

import ollama

from shiny.express import ui

# Set some Shiny page options
ui.page_opts(
    title="Hello Ollama Chat",
    fillable=True,
    fillable_mobile=True,
)

# Create and display empty chat
chat = ui.Chat(id="chat")
chat.ui()


# Define a callback to run when the user submits a message
@chat.on_user_submit
async def _():
    # Get messages currently in the chat
    messages = chat.messages(format="ollama")
    # Create a response message stream
    # Assumes you've run `ollama run llama3` to start the server
    response = ollama.chat(
        model="llama3",
        messages=messages,
        stream=True,
    )
    # Append the response stream into the chat
    await chat.append_message_stream(response)
Screenshot 2024-11-26 at 11 12 33 AM

@cpsievert cpsievert requested a review from gadenbuie November 26, 2024 17:18
Comment on lines 48 to 67
def get_default_tokenizer() -> TokenizersTokenizer:
try:
from tokenizers import Tokenizer

return Tokenizer.from_pretrained("bert-base-cased") # type: ignore
except Exception:
pass

return None
except ImportError:
raise ValueError(
"Failed to download a default tokenizer. "
"A tokenizer is required to impose `token_limits` on `chat.messages()`. "
"To get a generic default tokenizer, install the `tokenizers` "
"package (`pip install tokenizers`). "
)
except Exception as e:
raise ValueError(
"Failed to download a default tokenizer. "
"A tokenizer is required to impose `token_limits` on `chat.messages()`. "
"Try downloading a different tokenizer using "
"`tokenizers.Tokenizer.from_pretrained()`. "
f"Error: {e}"
) from e
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This change is orthogonal to the main fix of this PR, but it's a good idea, and I ran into because bert-base-cased temporarily went offline for a couple of the test runs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Are token_limits always imposed? Or is that opt-in or opt-out? It's hard to follow the thread back to where that would be chosen by the user and you might want to include that information in the message, e.g. if it's possible to disable token limits.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, it's opt-in

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like the second message is missing some instruction about how to choose a specific tokenizer that was in the original message. I also think it'd be nice to include how to turn off the need for a tokenizer, but that might be obvious from the code you'd write to get here.

Comment on lines +239 to +244
if isinstance(ChatResponse, dict):
return "message" in message and super().can_normalize(
message["message"]
)
else:
return isinstance(message, ChatResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is some of the context here that you have a message normalizer for Pydantic models?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think you necessarily need to leave a comment, but it'd be helpful for my understanding of the code if you just quickly explained how this fixes the problem (beyond the simple explanation that before it was a dict and now it isn't, that part I get).

Copy link
Collaborator Author

@cpsievert cpsievert Nov 26, 2024

Choose a reason for hiding this comment

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

DictNormalizer works for either case since ollama defines __getitem__() on the pydantic model. I suppose that is a weird/subtle thing that requires extra context, and it'd be nice to take advantage of stronger pydantic typing, but I opted for the minimal change (especially if we're going to support older versions)

Copy link
Collaborator

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

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

My comments are all small and non-blocking

@cpsievert cpsievert enabled auto-merge (squash) November 26, 2024 19:44
@cpsievert cpsievert merged commit 46d8ab8 into main Nov 26, 2024
40 checks passed
@cpsievert cpsievert deleted the ollama-0.4-fix branch November 26, 2024 19:50
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.

2 participants