Skip to content

Commit

Permalink
Merge pull request #128 from PrefectHQ/python3dot9
Browse files Browse the repository at this point in the history
  • Loading branch information
jlowin authored Apr 1, 2023
2 parents 1626909 + 77e4454 commit b94c484
Show file tree
Hide file tree
Showing 18 changed files with 75 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.9"
- uses: actions/cache@v2
with:
key: ${{ github.ref }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.9"
- uses: actions/cache@v2
with:
key: ${{ github.ref }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/run-llm-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.9"
cache: "pip" # caching pip dependencies
cache-dependency-path: "**/pyproject.toml"
- name: Install Marvin
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.9"
cache: "pip" # caching pip dependencies
cache-dependency-path: "**/pyproject.toml"
- name: Install Marvin
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.9"
- name: Run pre-commit
uses: pre-commit/[email protected]
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ repos:
hooks:
- id: black
args: ["--preview"]
language_version: python3.10
language_version: python3.9
4 changes: 3 additions & 1 deletion docs/getting_started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

## Requirements

Marvin requires Python 3.10. While nothing in Marvin should prevent use with Python 3.11, some dependencies may not be fully compatible yet.
Marvin requires Python 3.9+.
## For normal use
To install Marvin, run `pip install marvin`. As a matter of best practice, we recommend installing Marvin in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/).

To use Marvin's [knowledge features](../guide/concepts/infra.md), please include the [Chroma](https://www.trychroma.com/) dependency: `pip install marvin[chromadb]`.

Before using Marvin, you'll need to [configure your OpenAI API key](openai.md).

## For development
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ classifiers = [
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
keywords = ["ai", "chatbot", "llm"]
requires-python = ">=3.10"
requires-python = ">=3.9"
dependencies = [
"aiofiles~=23.1.0",
"aiosqlite~=0.18.0",
Expand Down
2 changes: 1 addition & 1 deletion src/marvin/api/bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async def create_bot_config(
async def get_bot_config(
name: str,
session: AsyncSession = Depends(fastapi_session),
) -> BotConfigRead | None:
) -> BotConfigRead:
result = await session.execute(
sa.select(BotConfig).where(BotConfig.name == name).limit(1)
)
Expand Down
4 changes: 2 additions & 2 deletions src/marvin/api/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async def create_thread(
async def get_thread_by_lookup_key(
lookup_key: str,
session: AsyncSession = Depends(fastapi_session),
) -> Thread | None:
) -> Thread:
result = await session.execute(
sa.select(Thread).where(Thread.lookup_key == lookup_key).limit(1)
)
Expand All @@ -48,7 +48,7 @@ async def get_thread_by_lookup_key(
async def get_thread(
thread_id: ThreadID = Path(..., alias="id"),
session: AsyncSession = Depends(fastapi_session),
) -> Thread | None:
) -> Thread:
result = await session.execute(
sa.select(Thread).where(Thread.id == thread_id).limit(1)
)
Expand Down
1 change: 0 additions & 1 deletion src/marvin/bots/ai_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def rhyme(word: str) -> str:
- bot_kwargs (dict): kwargs to pass to the `Bot` constructor
"""

# this allows the decorator to be used with or without calling it
if fn is None:
return partial(
Expand Down
46 changes: 25 additions & 21 deletions src/marvin/bots/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class BotResponse(BaseMessage):
parsed_content: Any = None


MAX_VALIDATION_ATTEMPTS = 3

if TYPE_CHECKING:
from marvin.models.bots import BotConfig
DEFAULT_NAME = "Marvin"
Expand Down Expand Up @@ -312,31 +314,33 @@ async def say(self, *args, response_format=None, **kwargs) -> BotResponse:
# validate response format
parsed_response = response
validated = False
validation_attempts = 0

while not validated and validation_attempts < 3:
validation_attempts += 1
for _ in range(MAX_VALIDATION_ATTEMPTS):
try:
self.response_format.validate_response(response)
validated = True
break
except Exception as exc:
match self.response_format.on_error:
case "ignore":
validated = True
case "raise":
raise exc
case "reformat":
self.logger.debug(
"Response did not pass validation. Attempted to reformat:"
f" {response}"
)
reformatted_response = _reformat_response(
user_message=user_message.content,
ai_response=response,
error_message=repr(exc),
target_return_type=self.response_format.format,
)
response = str(reformatted_response)
on_error = self.response_format.on_error
if on_error == "ignore":
break
elif on_error == "raise":
raise exc
elif on_error == "reformat":
self.logger.debug(
"Response did not pass validation. Attempted to reformat:"
f" {response}"
)
reformatted_response = _reformat_response(
user_message=user_message.content,
ai_response=response,
error_message=repr(exc),
target_return_type=self.response_format.format,
)
response = str(reformatted_response)
else:
raise ValueError(f"Unknown on_error value: {on_error}")
else:
raise RuntimeError("Failed to validate response after 3 attempts")

if validated:
parsed_response = self.response_format.parse_response(response)
Expand Down
57 changes: 21 additions & 36 deletions src/marvin/bots/response_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
import warnings
from types import GenericAlias
from typing import Any, Literal
from typing import Any, Literal, Union

import pydantic
from pydantic import BaseModel, Field, PrivateAttr
Expand Down Expand Up @@ -56,7 +56,7 @@ def parse_response(self, response):


class TypeFormatter(ResponseFormatter):
_cached_type: type | GenericAlias = PrivateAttr(SENTINEL)
_cached_type: Union[type, GenericAlias] = PrivateAttr(SENTINEL)
type_schema: dict[str, Any] = Field(
..., description="The OpenAPI schema for the type"
)
Expand Down Expand Up @@ -101,7 +101,7 @@ def __init__(self, type_: type = SENTINEL, **kwargs):
if type_ is not SENTINEL:
self._cached_type = type_

def get_type(self) -> type | GenericAlias:
def get_type(self) -> Union[type, GenericAlias]:
if self._cached_type is not SENTINEL:
return self._cached_type

Expand Down Expand Up @@ -132,7 +132,7 @@ class PydanticFormatter(ResponseFormatter):
..., description="The OpenAPI schema for the model"
)

def __init__(self, model: BaseModel | GenericAlias = None, **kwargs):
def __init__(self, model: Union[BaseModel, GenericAlias] = None, **kwargs):
if model is not None:
for key in ["format", "type_schema"]:
if key in kwargs:
Expand Down Expand Up @@ -172,35 +172,20 @@ def parse_response(self, response):


def load_formatter_from_shorthand(shorthand_response_format) -> ResponseFormatter:
match shorthand_response_format:
# shorthand for None
case None:
return ResponseFormatter()

# x is a ResponseFormatter - no shorthand
case x if isinstance(x, ResponseFormatter):
return x

# x is a boolean
case x if x is bool:
return BooleanFormatter()

# x is a string that contains the word "json"
case x if isinstance(x, str) and re.search(r"\bjson\b", x.lower()):
return JSONFormatter(format=x)

# x is a string
case x if isinstance(x, str):
return ResponseFormatter(format=x)

# x is a pydantic model
case x if genericalias_contains(x, pydantic.BaseModel):
return PydanticFormatter(model=x)

# x is a type or GenericAlias
case x if isinstance(x, (type, GenericAlias)):
return TypeFormatter(type_=x)

# unsupported values
case _:
raise ValueError("Invalid output format")
if shorthand_response_format is None:
return ResponseFormatter()
elif isinstance(shorthand_response_format, ResponseFormatter):
return shorthand_response_format
elif shorthand_response_format is bool:
return BooleanFormatter()
elif isinstance(shorthand_response_format, str):
if re.search(r"\bjson\b", shorthand_response_format.lower()):
return JSONFormatter(format=shorthand_response_format)
else:
return ResponseFormatter(format=shorthand_response_format)
elif genericalias_contains(shorthand_response_format, pydantic.BaseModel):
return PydanticFormatter(model=shorthand_response_format)
elif isinstance(shorthand_response_format, (type, GenericAlias)):
return TypeFormatter(type_=shorthand_response_format)
else:
raise ValueError("Invalid output format")
4 changes: 2 additions & 2 deletions src/marvin/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from contextlib import contextmanager
from pathlib import Path
from typing import Literal
from typing import Literal, Optional

try:
import chromadb
Expand Down Expand Up @@ -64,7 +64,7 @@ def export_to_env_file(self):
# LOGGING
verbose: bool = False
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
log_console_width: int | None = Field(
log_console_width: Optional[int] = Field(
None,
description=(
"Marvin will auto-detect the console width when possible, but in deployed"
Expand Down
4 changes: 2 additions & 2 deletions src/marvin/loaders/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import functools
import re
from pathlib import Path
from typing import Dict, List, Tuple
from typing import Dict, List, Optional, Tuple

import aiofiles
import chardet
Expand Down Expand Up @@ -55,7 +55,7 @@ class GitHubIssue(BaseModel):
html_url: str = Field(...)
number: int = Field(...)
title: str = Field(default="")
body: str | None = Field(default="")
body: Optional[str] = Field(default="")
labels: List[GitHubLabel] = Field(default_factory=GitHubLabel)
user: GitHubUser = Field(default_factory=GitHubUser)

Expand Down
11 changes: 6 additions & 5 deletions src/marvin/models/documents.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import inspect
from typing import Optional

from jinja2 import Template
from pydantic import Field, confloat, validator
Expand Down Expand Up @@ -95,15 +96,15 @@ class Document(MarvinBaseModel):
text: str = Field(
..., description="Any text content that you want to keep / embed."
)
embedding: list[float] | None = Field(default=None)
embedding: Optional[list[float]] = Field(default=None)
metadata: Metadata = Field(default_factory=Metadata)

source: str | None = Field(default=None)
source: Optional[str] = Field(default=None)
type: DocumentType = Field(default="original")
parent_document_id: DocumentID | None = Field(default=None)
parent_document_id: Optional[DocumentID] = Field(default=None)
topic_name: str = Field(default=marvin.settings.default_topic)
tokens: int | None = Field(default=None)
order: int | None = Field(default=None)
tokens: Optional[int] = Field(default=None)
order: Optional[int] = Field(default=None)
keywords: list[str] = Field(default_factory=list)

@validator("tokens", pre=True, always=True)
Expand Down
6 changes: 3 additions & 3 deletions src/marvin/plugins/chroma.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Callable
from typing import Any, Callable, Optional

import pendulum
from pydantic import Field
Expand Down Expand Up @@ -111,8 +111,8 @@ def iso_to_timestamp(filter: dict) -> float:
@plugin
async def chroma_search(
query: str,
where: dict[str, Any] | None = None,
where_document: dict[str, Any] | None = None,
where: Optional[dict[str, Any]] = None,
where_document: Optional[dict[str, Any]] = None,
) -> str:
"""
query (str): A verbose natural language query.
Expand Down
2 changes: 1 addition & 1 deletion src/marvin/utilities/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def split_text(
chunk_overlap: float = None,
last_chunk_threshold: float = None,
return_index: bool = False,
) -> str | tuple[str, int]:
) -> Union[str, tuple[str, int]]:
"""
Split a text into a list of strings. Chunks are split by tokens.
Expand Down

0 comments on commit b94c484

Please sign in to comment.