-
Notifications
You must be signed in to change notification settings - Fork 350
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #917 from PrefectHQ/logfire
add logfire example
- Loading branch information
Showing
11 changed files
with
231 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
## using logfire with marvin | ||
|
||
[logfire](https://github.com/pydantic/logfire?tab=readme-ov-file#pydantic-logfire--uncomplicated-observability) is brand new (Apr 2024) and is an observability tool for python applications - [otel](https://opentelemetry.io/docs/what-is-opentelemetry/)-based tracing, metrics, and logging. its pretty [awesome](https://docs.pydantic.dev/logfire/#pydantic-logfire-the-observability-platform-you-deserve). | ||
|
||
they also happen to wrap OpenAI pretty well out of the box! see `hello.py` for a simple example. | ||
|
||
### setup | ||
```conosle | ||
pip install marvin | ||
``` | ||
> [!NOTE] | ||
> optionally, if you want to try out the fastapi integration | ||
> ```console | ||
> pip install 'logfire[fastapi]' uvicorn | ||
> ``` | ||
login to logfire | ||
```console | ||
logfire auth | ||
``` | ||
### usage | ||
use of marvin should be no different than any other library. check out [logfire's documentation](https://docs.pydantic.dev/logfire/#pydantic-logfire-the-observability-platform-you-deserve) for more information. | ||
|
||
|
||
### examples | ||
```console | ||
gh repo clone prefecthq/marvin && cd marvin | ||
uvicorn cookbook.logfire.demo_app:app | ||
``` | ||
|
||
in another terminal | ||
```console | ||
python cookbook/logfire/send_demo_request.py | ||
``` | ||
|
||
check out the api docs at http://localhost:8000/docs or your logfire dashboard to see the traces and logs like: | ||
|
||
<p align="center"> | ||
<img src="/docs/assets/images/docs/examples/logfire-span.jpeg" alt="logfire span"/> | ||
</p> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import logfire | ||
|
||
logfire.install_auto_tracing(modules=["hello"]) | ||
|
||
from hello import main # noqa | ||
|
||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from enum import Enum | ||
|
||
import logfire | ||
import openai | ||
from fastapi import Body, FastAPI | ||
from marvin import fn | ||
from marvin.client import AsyncMarvinClient | ||
from pydantic import BaseModel | ||
|
||
app = FastAPI() | ||
client = openai.AsyncClient() | ||
|
||
logfire.configure(pydantic_plugin=logfire.PydanticPlugin(record="all")) | ||
logfire.instrument_openai(client) | ||
logfire.instrument_fastapi(app) | ||
|
||
|
||
class Seniority(Enum): | ||
"""ranked seniority levels for candidates""" | ||
|
||
JUNIOR = 1 | ||
MID = 2 | ||
SENIOR = 3 | ||
STAFF = 4 | ||
|
||
|
||
class Candidate(BaseModel): | ||
name: str | ||
self_identified_seniority: Seniority | ||
bio: str | ||
|
||
|
||
class Role(BaseModel): | ||
title: str | ||
desired_seniority: Seniority | ||
description: str | ||
|
||
|
||
@fn(client=AsyncMarvinClient(client=client)) | ||
def choose_among_candidates(cohort: list[Candidate], role: Role) -> Candidate: | ||
return ( | ||
f"We need a {role.desired_seniority.name} (at least) {role.title} that can " | ||
f"most likely fulfill a job of this description:\n{role.description}\n" | ||
) | ||
|
||
|
||
@logfire.instrument("Dystopian Interview Process", extract_args=True) | ||
def dystopian_interview_process(candidates: list[Candidate], role: Role) -> Candidate: | ||
senior_enough_candidates = [ | ||
candidate | ||
for candidate in candidates | ||
if candidate.self_identified_seniority.value >= role.desired_seniority.value | ||
] | ||
logfire.info( | ||
"Candidates at or above {seniority} level: {cohort}", | ||
cohort=[c.name for c in senior_enough_candidates], | ||
seniority=role.desired_seniority, | ||
) | ||
if len(senior_enough_candidates) == 1: | ||
return senior_enough_candidates[0] | ||
|
||
with logfire.span("Choosing among candidates"): | ||
return choose_among_candidates(senior_enough_candidates, role) | ||
|
||
|
||
@app.post("/interview") | ||
async def interview( | ||
candidates: list[Candidate] = Body(..., description="List of candidates"), | ||
role: Role = Body(..., description="Role to fill"), | ||
) -> Candidate: | ||
best_candidate = dystopian_interview_process(candidates, role) | ||
logfire.info("Best candidate: {best_candidate}", best_candidate=best_candidate) | ||
return best_candidate | ||
|
||
|
||
if __name__ == "__main__": | ||
import uvicorn | ||
|
||
uvicorn.run(app, host="localhost", port=8000) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
""" | ||
Example of using logfire to instrument OpenAI API calls | ||
see https://x.com/Nathan_Nowack/status/1785413529232708087 | ||
""" | ||
|
||
import logfire | ||
import openai | ||
from marvin import fn | ||
from marvin.client import AsyncMarvinClient | ||
from pydantic import BaseModel, Field | ||
|
||
client = openai.AsyncClient() | ||
|
||
logfire.instrument_openai(client) | ||
|
||
|
||
class Ingredients(BaseModel): | ||
name: str | ||
approximate_price: float = Field(..., gt=0, description="Price in USD") | ||
quantity: int | ||
|
||
|
||
class Recipe(BaseModel): | ||
ingredients: list[Ingredients] | ||
steps: list[str] | ||
|
||
|
||
@fn(client=AsyncMarvinClient(client=client)) | ||
def make_recipe(vibe: str) -> Recipe: | ||
"""Generate a recipe based on a vibe""" | ||
|
||
|
||
def main(): | ||
recipe = make_recipe("italian, for 4 people") | ||
assert isinstance(recipe, Recipe) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import asyncio | ||
import json | ||
|
||
import httpx | ||
|
||
|
||
async def main(): | ||
candidates = [ | ||
{ | ||
"name": "Alice", | ||
"self_identified_seniority": 3, | ||
"bio": "10 years with postgres, 5 years with python, 3 years with django.", | ||
}, | ||
{ | ||
"name": "Bob", | ||
"self_identified_seniority": 1, | ||
"bio": "I just graduated from a coding bootcamp and I'm ready to take on the world!", | ||
}, | ||
{ | ||
"name": "Charlie", | ||
"self_identified_seniority": 2, | ||
"bio": "graduated 2 years ago and i can make you a react app in no time", | ||
}, | ||
{ | ||
"name": "David", | ||
"self_identified_seniority": 3, | ||
"bio": "i just been riding that SCRUM wave for 10 years fam", | ||
}, | ||
] | ||
|
||
role = { | ||
"title": "Senior Software Engineer", | ||
"desired_seniority": 3, | ||
"description": "Build and maintain a large-scale web application with a team of 10+ engineers.", | ||
} | ||
|
||
async with httpx.AsyncClient() as client: | ||
response = await client.post( | ||
"http://localhost:8000/interview", | ||
json={"candidates": candidates, "role": role}, | ||
) | ||
result = response.json() | ||
print(json.dumps(result, indent=2)) | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters