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

update api ref / docstrings #725

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api_reference/components/prompt_fn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: marvin.components.prompt.fn
1 change: 1 addition & 0 deletions docs/api_reference/components/text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: marvin.components.text
9 changes: 5 additions & 4 deletions docs/api_reference/index.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Sections

## Components
- [AI Classifiers](/api_reference/components/classifier/)
- [AI Functions](/api_reference/components/ai_function/)
- [AI Models](/api_reference/components/ai_model/)

- [Classifier](/api_reference/components/classifier/)
- [Function](/api_reference/components/function/)
- [Model](/api_reference/components/model/)
- [Prompt Function](/api_reference/components/prompt_fn/)
- [Text](/api_reference/components/text/)
## Settings
- [Settings](/api_reference/settings/)

Expand Down
4 changes: 2 additions & 2 deletions docs/examples/slackbot.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ In our case of the Prefect Community slackbot, we want:
This runs 24/7 in the #ask-marvin channel of the Prefect Community Slack. It responds to users in a thread, and has memory of previous messages by slack thread. It uses the `chroma` and `github` tools for RAG to answer questions about Prefect 2.x.

```python
async def handle_message(payload: SlackPayload):
async def handle_message(payload: SlackPayload): # SlackPayload is a pydantic model
logger = get_logger("slackbot")
user_message = (event := payload.event).text
cleaned_message = re.sub(BOT_MENTION, "", user_message).strip()
Expand Down Expand Up @@ -158,4 +158,4 @@ CMD ["python", "cookbook/slackbot/start.py"]
Note that we're installing the `slackbot` extras here, which are required for tools used by the worker bot defined in this example's `cookbook/slackbot/start.py` file.

## Find the whole example here
- [cookbook/slackbot/start.py](/cookbook/slackbot/start.py)
- [cookbook/slackbot/start.py](https://github.com/PrefectHQ/marvin/blob/main/cookbook/slackbot/start.py)
2 changes: 1 addition & 1 deletion docs/static/css/tailwind.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/

/*
Expand Down
48 changes: 48 additions & 0 deletions src/marvin/components/classifier.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
"""Component for classifying text.

```python
from typing import Literal
import marvin

Mood = Literal["happy", "sad", "passive aggressive", "neutral"]

@marvin.components.classifier
def classify_mood(text: str) -> Mood:
return f"Classify the mood of the text: {text}"

classify_mood("brain the size of a planet and they ask me to classify the mood of the text")
# "passive aggressive"
```
"""
import asyncio
from typing import (
Any,
Expand Down Expand Up @@ -27,6 +43,17 @@


class ClassifierKwargs(TypedDict):
"""Keyword arguments for the classifier decorator.

Attributes:
environment: The jinja environment to use for the classifier.
prompt: The prompt to use for the classifier.
encoder: The encoder to use for the classifier.
client: The client to use for the classifier.
aclient: The async client to use for the classifier.
model: The model to use for the classifier.
"""

environment: NotRequired[BaseEnvironment]
prompt: NotRequired[str]
encoder: NotRequired[Callable[[str], list[int]]]
Expand Down Expand Up @@ -170,6 +197,27 @@ def classifier(
],
Callable[P, Union[T, Coroutine[Any, Any, T]]],
]:
"""Decorator that turns a function into a classifier.

Args:
fn: The function to be converted into a classifier.
**kwargs: `ClassifierKwargs` to be passed to the classifier.

Example:
Create a functional classifier that returns the most appropriate `Literal` or `Enum` member:
```python
from typing import Literal
import marvin

Mood = Literal["happy", "sad", "angry", "neutral"]

@marvin.components.classifier
def classify_mood(text: str) -> Mood:
return f"Classify the mood of the text: {text}"

classify_mood("this pequod's pizza is bussin") # "happy"
```
"""
if fn is not None:
return Classifier[P, T].as_decorator(
fn=fn, **ClassifierKwargsDefaults(**kwargs).model_dump(exclude_none=True)
Expand Down
85 changes: 81 additions & 4 deletions src/marvin/components/function.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
"""Function component for creating type-safe, functional prompts for LLMs.

```python
import marvin
from typing_extensions import TypedDict

class Fruit(TypedDict):
name: str
color: str

@marvin.fn
def list_fruit(n: int) -> list[Fruit]:
'''Returns a list of `n` fruit'''

list_fruit(2)
# [{'name': 'apple', 'color': 'red'}, {'name': 'banana', 'color': 'yellow'}]
```
"""
import asyncio
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -38,6 +56,21 @@


class FunctionKwargs(TypedDict):
"""Keyword arguments for the function decorator.

Attributes:
environment: The jinja environment to use for the function.
prompt: The prompt to use for the function.
model_name: The model name to use for the function.
model_description: The model description to use for the function.
field_name: The field name to use for the function.
field_description: The field description to use for the function.
client: The client to use for the function.
aclient: The async client to use for the function.
model: The model to use for the function.
temperature: The temperature to use for the function.
"""

environment: NotRequired[BaseEnvironment]
prompt: NotRequired[str]
model_name: NotRequired[str]
Expand Down Expand Up @@ -117,20 +150,45 @@ async def acall(self, *args: P.args, **kwargs: P.kwargs) -> T:
@expose_sync_method("map")
async def amap(self, *map_args: list[Any], **map_kwargs: list[Any]) -> list[T]:
"""
Map the AI function over a sequence of arguments. Runs concurrently.
Map the AI function over a sequence of arguments - runs concurrently.

A `map` twin method is provided by the `expose_sync_method` decorator.

You can use `map` or `amap` synchronously or asynchronously, respectively,
regardless of whether the user function is synchronous or asynchronous.
regardless of whether the decorated function is synchronous or asynchronous.

Arguments should be provided as if calling the function normally, but
each argument must be a list. The function is called once for each item
in the list, and the results are returned in a list.

For example, fn.map([1, 2]) is equivalent to [fn(1), fn(2)].
For example:

- `fn.map([1, 2])` == `[fn(1), fn(2)]`.

- `fn.map([1, 2], x=['a', 'b'])` == `[fn(1, x='a'), fn(2, x='b')]`.

Args:
*map_args: The positional arguments to map.
**map_kwargs: The keyword arguments to map.

fn.map([1, 2], x=['a', 'b']) is equivalent to [fn(1, x='a'), fn(2, x='b')].
Returns:
The results of the mapped function calls.

Example:
```python
import marvin

@marvin.fn
def list_fruit(n: int) -> list[str]:
'''Returns a list of `n` fruit'''

list_of_lists_of_fruit = list_fruit.map([2, 3])
print(list_of_lists_of_fruit) # [['apple', 'banana'], ['apple', 'banana', 'cherry']]

assert len(list_of_lists_of_fruit) == 2
assert len(list_of_lists_of_fruit[0]) == 2
assert len(list_of_lists_of_fruit[1]) == 3
```
"""
tasks: list[Any] = []
if map_args and map_kwargs:
Expand Down Expand Up @@ -234,6 +292,25 @@ def fn(
],
Callable[P, Union[T, Coroutine[Any, Any, T]]],
]:
"""Decorator for creating a type-safe, functional prompt for an LLM.

Args:
fn: The function to decorate.
**kwargs: Keyword arguments for the function decorator.

Example:
Get first `n` digits of pi:
```python
import marvin

@marvin.fn
def get_pi(n: int) -> float:
'''Return the first n digits of pi'''

assert get_pi(5) == 3.14159
```
"""

if fn is not None:
return Function[P, T].as_decorator(
fn=fn, **FunctionKwargsDefaults(**kwargs).model_dump(exclude_none=True)
Expand Down
36 changes: 36 additions & 0 deletions src/marvin/components/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,42 @@ def model(
partial[Callable[[Callable[[str], T]], Callable[[str], T]]],
Callable[[str], T],
]:
"""Decorator for creating a Pydantic model type that can be used to cast or extract data.

Args:
_type: The type of the model to create.
**kwargs: Keyword arguments to pass to the model.

Returns:
A Pydantic model type that can be used to cast or extract data.

Example:
```python
import marvin
from pydantic import BaseModel

class MenuItem(BaseModel):
name: str
price: float

class Order(BaseModel):
items: list[MenuItem]
total: float

marvin.model(Order)("can i get 2 $5 footlongs? and 2 cookies from the dollar menu?")
'''
Order(
items=[
MenuItem(name='footlong', price=5.0),
MenuItem(name='footlong', price=5.0),
MenuItem(name='cookie', price=1.0),
MenuItem(name='cookie', price=1.0)
],
total=12.0
)
'''
```
"""
if _type is not None:

def extract(text: str, instructions: str = None) -> _type:
Expand Down
39 changes: 39 additions & 0 deletions src/marvin/components/prompt/fn.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
"""Component for creating prompts from functions.

```python
from marvin import prompt_fn

@prompt_fn
def list_fruits(n: int, color: str = 'red') -> list[str]:
'''Generates a list of {{ n }} {{ color }} fruits.'''

list_fruits(3, 'blue')
'''
{'tools': [{'type': 'function',
'function': {'name': 'FormatResponse',
'description': 'Formats the response.',
'parameters': {'description': 'Formats the response.',
'properties': {'data': {'description': 'The data to format.',
'items': {'type': 'string'},
'title': 'Data',
'type': 'array'}},
'required': ['data'],
'type': 'object'}}}],
'tool_choice': {'type': 'function', 'function': {'name': 'FormatResponse'}},
'messages': [{'content': 'Generates a list of 3 blue fruits.',
'role': 'system'}]}
'''
```
"""
import inspect
import re
from functools import partial, wraps
Expand Down Expand Up @@ -271,6 +298,18 @@ def prompt_fn(
Callable[[Callable[P, T]], Callable[P, dict[str, Any]]],
Callable[P, dict[str, Any]],
]:
"""Decorator for creating prompts from functions.

Args:
fn: The function to decorate.
environment: The jinja environment to use for rendering the prompt.
prompt: The prompt to use. Defaults to the function's docstring.
model_name: The name of the model.
model_description: The description of the model.
field_name: The name of the output field.
field_description: The description of the output field.
"""

def wrapper(
func: Callable[P, Any], *args: P.args, **kwargs: P.kwargs
) -> dict[str, Any]:
Expand Down
Loading