Skip to content

Commit

Permalink
Merge branch 'main' into instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
jlowin committed Mar 11, 2024
2 parents bb9d51f + 9601033 commit b4f2a54
Show file tree
Hide file tree
Showing 30 changed files with 989 additions and 123 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ jobs:
with:
key: ${{ github.ref }}
path: .cache
- name: Install uv
run: pip install -U uv && uv venv

- name: Install Material Insiders
run: pip install git+https://oauth:${MKDOCS_MATERIAL_INSIDERS_REPO_RO}@github.com/PrefectHQ/mkdocs-material-insiders.git

# for now, only install mkdocs. In the future may need to install Marvin itself.
- name: Install dependencies for MKDocs Material
run: pip install \
git+https://oauth:${MKDOCS_MATERIAL_INSIDERS_REPO_RO}@github.com/PrefectHQ/mkdocs-material-insiders.git \
run: uv pip install \
mkdocs-autolinks-plugin \
mkdocs-awesome-pages-plugin \
mkdocs-markdownextradata-plugin \
Expand All @@ -42,4 +47,4 @@ jobs:
cairosvg
- name: Build docs
run: |
mkdocs build --config-file mkdocs.insiders.yml
mkdocs build --config-file mkdocs.insiders.yml
8 changes: 6 additions & 2 deletions .github/workflows/publish-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ jobs:
with:
key: ${{ github.ref }}
path: .cache

- name: Install uv
run: pip install -U uv && uv venv

# for now, only install mkdocs. In the future may need to install Marvin itself.
- name: Install dependencies for MKDocs Material
run: pip install \
run: uv pip install \
mkdocs-material \
mkdocs-autolinks-plugin \
mkdocs-awesome-pages-plugin \
Expand All @@ -36,4 +40,4 @@ jobs:
pillow \
cairosvg
- name: Publish docs
run: mkdocs gh-deploy --force
run: mkdocs gh-deploy --force
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ Marvin consists of a variety of useful tools, all designed to be used independen

### Audio

🎙️ [Generate speech](https://askmarvin.ai/docs/audio/speech) from text or functions
💬 [Generate speech](https://askmarvin.ai/docs/audio/speech) from text or functions

✍️ [Transcribe speech](https://askmarvin.ai/docs/audio/transcription) from recorded audio

🎙️ [Record users](https://askmarvin.ai/docs/audio/recording) continuously or as individual phrases

### Video

🎙️ [Record video](https://askmarvin.ai/docs/video/recording) continuously

### Interaction

Expand Down Expand Up @@ -108,7 +116,7 @@ marvin.extract("I moved from NY to CHI", target=Location)

# [
# Location(city="New York", state="New York"),
# Location(city="Chcago", state="Illinois")
# Location(city="Chicago", state="Illinois")
# ]
```

Expand Down Expand Up @@ -241,6 +249,30 @@ marvin.beta.classify(
# "drink"
```

## Record the user, modify the content, and play it back

Marvin can transcribe speech and generate audio out-of-the-box, but the optional `audio` extra provides utilities for recording and playing audio.

```python
import marvin
import marvin.audio

# record the user
user_audio = marvin.audio.record_phrase()

# transcribe the text
user_text = marvin.transcribe(user_audio)

# cast the language to a more formal style
ai_text = marvin.cast(user_text, instructions='Make the language ridiculously formal')

# generate AI speech
ai_audio = marvin.speak(ai_text)

# play the result
ai_audio.play()
```

# Get in touch!

💡 **Feature idea?** share it in the `#development` channel in [our Discord](https://discord.com/invite/Kgw4HpcuYG).
Expand Down
10 changes: 6 additions & 4 deletions cookbook/flows/insurance_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
authored by: @kevingrismore and @zzstoatzz
"""

from enum import Enum
from typing import TypeVar

Expand Down Expand Up @@ -52,11 +53,11 @@ def build_damage_report_model(damages: list[DamagedPart]) -> type[M]:
@task(cache_key_fn=task_input_hash)
def marvin_extract_damages_from_url(image_url: str) -> list[DamagedPart]:
return marvin.beta.extract(
data=marvin.beta.Image(image_url),
data=marvin.beta.Image.from_url(image_url),
target=DamagedPart,
instructions=(
"Give extremely brief, high-level descriptions of the damage."
" Only include the 2 most significant damages, which may also be minor and/or moderate."
"Give extremely brief, high-level descriptions of the damage. Only include"
" the 2 most significant damages, which may also be minor and/or moderate."
# only want 2 damages for purposes of this example
),
)
Expand All @@ -75,7 +76,8 @@ def submit_damage_report(report: M, car: Car):
description=f"## Latest damage report for car {car.id}",
)
print(
f"See your artifact in the UI: {PREFECT_UI_URL.value()}/artifacts/artifact/{uuid}"
"See your artifact in the UI:"
f" {PREFECT_UI_URL.value()}/artifacts/artifact/{uuid}"
)


Expand Down
70 changes: 54 additions & 16 deletions cookbook/flows/label_issues.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,67 @@
from enum import Enum

import marvin
from gh_util.functions import add_labels_to_issue, fetch_repo_labels
from gh_util.types import GitHubIssueEvent
from gh_util.types import GitHubIssueEvent, GitHubLabel
from prefect import flow, task
from prefect.events.schemas import DeploymentTrigger


@flow(log_prints=True)
async def label_issues(
event_body_str: str,
): # want to do {{ event.payload.body | from_json }} but not supported
"""Label issues based on their action"""
issue_event = GitHubIssueEvent.model_validate_json(event_body_str)
print(
f"Issue '#{issue_event.issue.number} - {issue_event.issue.title}' was {issue_event.action}"
@task
async def get_appropriate_labels(
issue_body: str, label_options: set[GitHubLabel], existing_labels: set[GitHubLabel]
) -> set[str]:
LabelOption = Enum(
"LabelOption",
{label.name: label.name for label in label_options.union(existing_labels)},
)

issue_body = issue_event.issue.body
@marvin.fn
async def get_labels(
body: str, existing_labels: list[GitHubLabel]
) -> set[LabelOption]: # type: ignore
"""Return appropriate labels for a GitHub issue based on its body.
If existing labels are sufficient, return them.
"""

return {i.value for i in await get_labels(issue_body, existing_labels)}


@flow(log_prints=True)
async def label_issues(event_body_json: str):
"""Label issues based on incoming webhook events from GitHub."""
event = GitHubIssueEvent.model_validate_json(event_body_json)

print(f"Issue '#{event.issue.number} - {event.issue.title}' was {event.action}")

owner, repo = event.repository.owner.login, event.repository.name

owner, repo = issue_event.repository.owner.login, issue_event.repository.name
label_options = await task(fetch_repo_labels)(owner, repo)

repo_labels = await task(fetch_repo_labels)(owner, repo)
labels = await get_appropriate_labels(
issue_body=event.issue.body,
label_options=label_options,
existing_labels=set(event.issue.labels),
)

label = task(marvin.classify)(
issue_body, labels=[label.name for label in repo_labels]
await task(add_labels_to_issue)(
owner=owner,
repo=repo,
issue_number=event.issue.number,
new_labels=labels,
)

await task(add_labels_to_issue)(owner, repo, issue_event.issue.number, {label})
print(f"Labeled issue with {' | '.join(labels)!r}")


print(f"Labeled issue with '{label}'")
if __name__ == "__main__":
label_issues.serve(
name="Label GitHub Issues",
triggers=[
DeploymentTrigger(
expect={"marvin.issue*"},
parameters={"event_body_json": "{{ event.payload.body }}"},
)
],
)
Binary file added docs/assets/audio/this_is_a_test.mp3
Binary file not shown.
Binary file added docs/assets/audio/this_is_a_test_2.mp3
Binary file not shown.
89 changes: 89 additions & 0 deletions docs/docs/audio/recording.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Recording audio

Marvin has utilities for working with audio data beyond generating speech and transcription. To use these utilities, you must install Marvin with the `audio` extra:

```bash
pip install marvin[audio]
```

## Audio objects

The `Audio` object gives users a simple way to work with audio data that is compatible with all of Marvin's audio abilities. You can create an `Audio` object from a file path or by providing audio bytes directly.


### From a file path
```python
from marvin.audio import Audio
audio = Audio.from_path("fancy_computer.mp3")
```
### From data
```python
audio = Audio(data=audio_bytes)
```

### Playing audio
You can play audio from an `Audio` object using the `play` method:

```python
audio.play()
```

## Recording audio

Marvin can record audio from your computer's microphone. There are a variety of options for recording audio in order to match your specific use case.



### Recording for a set duration

The basic `record` function records audio for a specified duration. The duration is provided in seconds.

```python
import marvin.audio

# record 5 seconds of audio
audio = marvin.audio.record(duration=5)
audio.play()
```

### Recording a phrase

The `record_phrase` function records audio until a pause is detected. This is useful for recording a phrase or sentence.

```python
import marvin.audio

audio = marvin.audio.record_phrase()
audio.play()
```

There are a few keyword arguments that can be used to customize the behavior of `record_phrase`:
- `after_phrase_silence`: The duration of silence to consider the end of a phrase. The default is 0.8 seconds.
- `timeout`: The maximum time to wait for speech to start before giving up. The default is no timeout.
- `max_phrase_duration`: The maximum duration for recording a phrase. The default is no limit.
- `adjust_for_ambient_noise`: Whether to adjust the recognizer sensitivity to ambient noise before starting recording. The default is `True`, but note that this introduces a minor latency between the time the function is called and the time recording starts. A log message will be printed to indicate when the calibration is complete.

### Recording continuously

The `record_background` function records audio continuously in the background. This is useful for recording audio while doing other tasks or processing audio in real time.

The result of `record_background` is a `BackgroundAudioRecorder` object, which can be used to control the recording (including stopping it) and to access the recorded audio as a stream.

By default, the audio is recorded as a series of phrases, meaning a new `Audio` object is created each time a phase is detected. Audio objects are queued and can be accessed by iterating over the recorder's `stream` method.

```python
import marvin
import marvin.audio

recorder = marvin.audio.record_background()

counter = 0
for audio in recorder.stream():
counter += 1
# process each audio phrase
marvin.transcribe(audio)

# stop recording
if counter == 3:
recorder.stop()
```
Loading

0 comments on commit b4f2a54

Please sign in to comment.