pydantic-ai-examples 0.0.6a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_ai_examples-0.0.6a1/.gitignore +15 -0
- pydantic_ai_examples-0.0.6a1/PKG-INFO +40 -0
- pydantic_ai_examples-0.0.6a1/README.md +5 -0
- pydantic_ai_examples-0.0.6a1/__main__.py +55 -0
- pydantic_ai_examples-0.0.6a1/bank_support.py +88 -0
- pydantic_ai_examples-0.0.6a1/chat_app.html +81 -0
- pydantic_ai_examples-0.0.6a1/chat_app.py +109 -0
- pydantic_ai_examples-0.0.6a1/chat_app.ts +90 -0
- pydantic_ai_examples-0.0.6a1/pydantic_model.py +33 -0
- pydantic_ai_examples-0.0.6a1/pyproject.toml +60 -0
- pydantic_ai_examples-0.0.6a1/rag.py +257 -0
- pydantic_ai_examples-0.0.6a1/roulette_wheel.py +68 -0
- pydantic_ai_examples-0.0.6a1/sql_gen.py +171 -0
- pydantic_ai_examples-0.0.6a1/stream_markdown.py +76 -0
- pydantic_ai_examples-0.0.6a1/stream_whales.py +99 -0
- pydantic_ai_examples-0.0.6a1/weather_agent.py +150 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pydantic-ai-examples
|
|
3
|
+
Version: 0.0.6a1
|
|
4
|
+
Summary: Agent Framework / shim to use Pydantic with LLMs
|
|
5
|
+
Author-email: Samuel Colvin <samuel@pydantic.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Environment :: MacOS X
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: Unix
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Internet
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: asyncpg>=0.30.0
|
|
28
|
+
Requires-Dist: fastapi>=0.115.4
|
|
29
|
+
Requires-Dist: logfire[asyncpg,fastapi]>=2.3
|
|
30
|
+
Requires-Dist: pydantic-ai-slim[groq,openai,vertexai]
|
|
31
|
+
Requires-Dist: python-multipart>=0.0.17
|
|
32
|
+
Requires-Dist: rich>=13.9.2
|
|
33
|
+
Requires-Dist: uvicorn>=0.32.0
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Pydantic AI Examples
|
|
37
|
+
|
|
38
|
+
Examples of how to use Pydantic AI and what it can do.
|
|
39
|
+
|
|
40
|
+
Full documentation of these examples and how to run them is available at [ai.pydantic.dev/examples/](https://ai.pydantic.dev/examples/).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Very simply CLI to aid in running the examples, and for copying examples code to a new directory.
|
|
2
|
+
|
|
3
|
+
See README.md for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cli():
|
|
12
|
+
this_dir = Path(__file__).parent
|
|
13
|
+
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog='pydantic_ai_examples',
|
|
16
|
+
description=__doc__,
|
|
17
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'-v', '--version', action='store_true', help='show the version and exit'
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
'--copy-to', dest='DEST', help='Copy all examples to a new directory'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
if args.version:
|
|
28
|
+
from pydantic_ai import __version__
|
|
29
|
+
|
|
30
|
+
print(f'pydantic_ai v{__version__}')
|
|
31
|
+
elif args.DEST:
|
|
32
|
+
copy_to(this_dir, Path(args.DEST))
|
|
33
|
+
else:
|
|
34
|
+
parser.print_help()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def copy_to(this_dir: Path, dst: Path):
|
|
38
|
+
if dst.exists():
|
|
39
|
+
print(f'Error: destination path "{dst}" already exists', file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
dst.mkdir(parents=True)
|
|
43
|
+
|
|
44
|
+
count = 0
|
|
45
|
+
for file in this_dir.glob('*.*'):
|
|
46
|
+
with open(file, 'rb') as src_file:
|
|
47
|
+
with open(dst / file.name, 'wb') as dst_file:
|
|
48
|
+
dst_file.write(src_file.read())
|
|
49
|
+
count += 1
|
|
50
|
+
|
|
51
|
+
print(f'Copied {count} example files to "{dst}"')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == '__main__':
|
|
55
|
+
cli()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Small but complete example of using PydanticAI to build a support agent for a bank.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
|
|
5
|
+
uv run -m pydantic_ai_examples.bank_supports
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
from pydantic_ai import Agent, CallContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatabaseConn:
|
|
16
|
+
"""This is a fake database for example purposes.
|
|
17
|
+
|
|
18
|
+
In reality, you'd be connecting to an external database
|
|
19
|
+
(e.g. PostgreSQL) to get information about customers.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
async def customer_name(cls, *, id: int) -> str | None:
|
|
24
|
+
if id == 123:
|
|
25
|
+
return 'John'
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
async def customer_balance(cls, *, id: int, include_pending: bool) -> float:
|
|
29
|
+
if id == 123:
|
|
30
|
+
return 123.45
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError('Customer not found')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class SupportDependencies:
|
|
37
|
+
customer_id: int
|
|
38
|
+
db: DatabaseConn
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SupportResult(BaseModel):
|
|
42
|
+
support_advice: str = Field(description='Advice returned to the customer')
|
|
43
|
+
block_card: bool = Field(description='Whether to block their')
|
|
44
|
+
risk: int = Field(description='Risk level of query', ge=0, le=10)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
support_agent = Agent(
|
|
48
|
+
'openai:gpt-4o',
|
|
49
|
+
deps_type=SupportDependencies,
|
|
50
|
+
result_type=SupportResult,
|
|
51
|
+
system_prompt=(
|
|
52
|
+
'You are a support agent in our bank, give the '
|
|
53
|
+
'customer support and judge the risk level of their query. '
|
|
54
|
+
"Reply using the customer's name."
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@support_agent.system_prompt
|
|
60
|
+
async def add_customer_name(ctx: CallContext[SupportDependencies]) -> str:
|
|
61
|
+
customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
|
|
62
|
+
return f"The customer's name is {customer_name!r}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@support_agent.retriever
|
|
66
|
+
async def customer_balance(
|
|
67
|
+
ctx: CallContext[SupportDependencies], include_pending: bool
|
|
68
|
+
) -> str:
|
|
69
|
+
"""Returns the customer's current account balance."""
|
|
70
|
+
balance = await ctx.deps.db.customer_balance(
|
|
71
|
+
id=ctx.deps.customer_id,
|
|
72
|
+
include_pending=include_pending,
|
|
73
|
+
)
|
|
74
|
+
return f'${balance:.2f}'
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
deps = SupportDependencies(customer_id=123, db=DatabaseConn())
|
|
78
|
+
result = support_agent.run_sync('What is my balance?', deps=deps)
|
|
79
|
+
print(result.data)
|
|
80
|
+
"""
|
|
81
|
+
support_advice='Hello John, your current account balance, including pending transactions, is $123.45.' block_card=False risk=1
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
result = support_agent.run_sync('I just lost my card!', deps=deps)
|
|
85
|
+
print(result.data)
|
|
86
|
+
"""
|
|
87
|
+
support_advice="I'm sorry to hear that, John. We are temporarily blocking your card to prevent unauthorized transactions." block_card=True risk=8
|
|
88
|
+
"""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Chat App</title>
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
main {
|
|
10
|
+
max-width: 700px;
|
|
11
|
+
}
|
|
12
|
+
#conversation .user::before {
|
|
13
|
+
content: 'You asked: ';
|
|
14
|
+
font-weight: bold;
|
|
15
|
+
display: block;
|
|
16
|
+
}
|
|
17
|
+
#conversation .llm-response::before {
|
|
18
|
+
content: 'AI Response: ';
|
|
19
|
+
font-weight: bold;
|
|
20
|
+
display: block;
|
|
21
|
+
}
|
|
22
|
+
#spinner {
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transition: opacity 500ms ease-in;
|
|
25
|
+
width: 30px;
|
|
26
|
+
height: 30px;
|
|
27
|
+
border: 3px solid #222;
|
|
28
|
+
border-bottom-color: transparent;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
animation: rotation 1s linear infinite;
|
|
31
|
+
}
|
|
32
|
+
@keyframes rotation {
|
|
33
|
+
0% { transform: rotate(0deg); }
|
|
34
|
+
100% { transform: rotate(360deg); }
|
|
35
|
+
}
|
|
36
|
+
#spinner.active {
|
|
37
|
+
opacity: 1;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<main class="border rounded mx-auto my-5 p-4">
|
|
43
|
+
<h1>Chat App</h1>
|
|
44
|
+
<p>Ask me anything...</p>
|
|
45
|
+
<div id="conversation" class="px-2"></div>
|
|
46
|
+
<div class="d-flex justify-content-center mb-3">
|
|
47
|
+
<div id="spinner"></div>
|
|
48
|
+
</div>
|
|
49
|
+
<form method="post">
|
|
50
|
+
<input id="prompt-input" name="prompt" class="form-control"/>
|
|
51
|
+
<div class="d-flex justify-content-end">
|
|
52
|
+
<button class="btn btn-primary mt-2">Send</button>
|
|
53
|
+
</div>
|
|
54
|
+
</form>
|
|
55
|
+
<div id="error" class="d-none text-danger">
|
|
56
|
+
Error occurred, check the console for more information.
|
|
57
|
+
</div>
|
|
58
|
+
</main>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/5.6.3/typescript.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
62
|
+
<script type="module">
|
|
63
|
+
// to let me write TypeScript, without adding the burden of npm we do a dirty, non-production-ready hack
|
|
64
|
+
// and transpile the TypeScript code in the browser
|
|
65
|
+
// this is (arguably) A neat demo trick, but not suitable for production!
|
|
66
|
+
async function loadTs() {
|
|
67
|
+
const response = await fetch('/chat_app.ts');
|
|
68
|
+
const tsCode = await response.text();
|
|
69
|
+
const jsCode = window.ts.transpile(tsCode, { target: "es2015" });
|
|
70
|
+
let script = document.createElement('script');
|
|
71
|
+
script.type = 'module';
|
|
72
|
+
script.text = jsCode;
|
|
73
|
+
document.body.appendChild(script);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
loadTs().catch((e) => {
|
|
77
|
+
console.error(e);
|
|
78
|
+
document.getElementById('error').classList.remove('d-none');
|
|
79
|
+
document.getElementById('spinner').classList.remove('active');
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Simple chat app example build with FastAPI.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
|
|
5
|
+
uv run -m pydantic_ai_examples.chat_app
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import Iterator
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Annotated
|
|
12
|
+
|
|
13
|
+
import fastapi
|
|
14
|
+
import logfire
|
|
15
|
+
from fastapi.responses import HTMLResponse, Response, StreamingResponse
|
|
16
|
+
from pydantic import Field, TypeAdapter
|
|
17
|
+
|
|
18
|
+
from pydantic_ai import Agent
|
|
19
|
+
from pydantic_ai.messages import (
|
|
20
|
+
Message,
|
|
21
|
+
MessagesTypeAdapter,
|
|
22
|
+
ModelTextResponse,
|
|
23
|
+
UserPrompt,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
|
|
27
|
+
logfire.configure(send_to_logfire='if-token-present')
|
|
28
|
+
|
|
29
|
+
agent = Agent('openai:gpt-4o')
|
|
30
|
+
|
|
31
|
+
app = fastapi.FastAPI()
|
|
32
|
+
logfire.instrument_fastapi(app)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.get('/')
|
|
36
|
+
async def index() -> HTMLResponse:
|
|
37
|
+
return HTMLResponse((THIS_DIR / 'chat_app.html').read_bytes())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.get('/chat_app.ts')
|
|
41
|
+
async def main_ts() -> Response:
|
|
42
|
+
"""Get the raw typescript code, it's compiled in the browser, forgive me."""
|
|
43
|
+
return Response((THIS_DIR / 'chat_app.ts').read_bytes(), media_type='text/plain')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.get('/chat/')
|
|
47
|
+
async def get_chat() -> Response:
|
|
48
|
+
msgs = database.get_messages()
|
|
49
|
+
return Response(
|
|
50
|
+
b'\n'.join(MessageTypeAdapter.dump_json(m) for m in msgs),
|
|
51
|
+
media_type='text/plain',
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.post('/chat/')
|
|
56
|
+
async def post_chat(prompt: Annotated[str, fastapi.Form()]) -> StreamingResponse:
|
|
57
|
+
async def stream_messages():
|
|
58
|
+
"""Streams new line delimited JSON `Message`s to the client."""
|
|
59
|
+
# stream the user prompt so that can be displayed straight away
|
|
60
|
+
yield MessageTypeAdapter.dump_json(UserPrompt(content=prompt)) + b'\n'
|
|
61
|
+
# get the chat history so far to pass as context to the agent
|
|
62
|
+
messages = list(database.get_messages())
|
|
63
|
+
# run the agent with the user prompt and the chat history
|
|
64
|
+
async with agent.run_stream(prompt, message_history=messages) as result:
|
|
65
|
+
async for text in result.stream(debounce_by=0.01):
|
|
66
|
+
# text here is a `str` and the frontend wants
|
|
67
|
+
# JSON encoded ModelTextResponse, so we create one
|
|
68
|
+
m = ModelTextResponse(content=text, timestamp=result.timestamp())
|
|
69
|
+
yield MessageTypeAdapter.dump_json(m) + b'\n'
|
|
70
|
+
|
|
71
|
+
# add new messages (e.g. the user prompt and the agent response in this case) to the database
|
|
72
|
+
database.add_messages(result.new_messages_json())
|
|
73
|
+
|
|
74
|
+
return StreamingResponse(stream_messages(), media_type='text/plain')
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
THIS_DIR = Path(__file__).parent
|
|
78
|
+
MessageTypeAdapter: TypeAdapter[Message] = TypeAdapter(
|
|
79
|
+
Annotated[Message, Field(discriminator='role')]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class Database:
|
|
85
|
+
"""Very rudimentary database to store chat messages in a JSON lines file."""
|
|
86
|
+
|
|
87
|
+
file: Path = THIS_DIR / '.chat_app_messages.jsonl'
|
|
88
|
+
|
|
89
|
+
def add_messages(self, messages: bytes):
|
|
90
|
+
with self.file.open('ab') as f:
|
|
91
|
+
f.write(messages + b'\n')
|
|
92
|
+
|
|
93
|
+
def get_messages(self) -> Iterator[Message]:
|
|
94
|
+
if self.file.exists():
|
|
95
|
+
with self.file.open('rb') as f:
|
|
96
|
+
for line in f:
|
|
97
|
+
if line:
|
|
98
|
+
yield from MessagesTypeAdapter.validate_json(line)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
database = Database()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == '__main__':
|
|
105
|
+
import uvicorn
|
|
106
|
+
|
|
107
|
+
uvicorn.run(
|
|
108
|
+
'pydantic_ai_examples.chat_app:app', reload=True, reload_dirs=[str(THIS_DIR)]
|
|
109
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// BIG FAT WARNING: to avoid the complexity of npm, this typescript is compiled in the browser
|
|
2
|
+
// there's currently no static type checking
|
|
3
|
+
|
|
4
|
+
import { marked } from 'https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.0/lib/marked.esm.js'
|
|
5
|
+
const convElement = document.getElementById('conversation')
|
|
6
|
+
|
|
7
|
+
const promptInput = document.getElementById('prompt-input') as HTMLInputElement
|
|
8
|
+
const spinner = document.getElementById('spinner')
|
|
9
|
+
|
|
10
|
+
// stream the response and render messages as each chunk is received
|
|
11
|
+
// data is sent as newline-delimited JSON
|
|
12
|
+
async function onFetchResponse(response: Response): Promise<void> {
|
|
13
|
+
let text = ''
|
|
14
|
+
let decoder = new TextDecoder()
|
|
15
|
+
if (response.ok) {
|
|
16
|
+
const reader = response.body.getReader()
|
|
17
|
+
while (true) {
|
|
18
|
+
const {done, value} = await reader.read()
|
|
19
|
+
if (done) {
|
|
20
|
+
break
|
|
21
|
+
}
|
|
22
|
+
text += decoder.decode(value)
|
|
23
|
+
addMessages(text)
|
|
24
|
+
spinner.classList.remove('active')
|
|
25
|
+
}
|
|
26
|
+
addMessages(text)
|
|
27
|
+
promptInput.disabled = false
|
|
28
|
+
promptInput.focus()
|
|
29
|
+
} else {
|
|
30
|
+
const text = await response.text()
|
|
31
|
+
console.error(`Unexpected response: ${response.status}`, {response, text})
|
|
32
|
+
throw new Error(`Unexpected response: ${response.status}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The format of messages, this matches pydantic-ai both for brevity and understanding
|
|
37
|
+
// in production, you might not want to keep this format all the way to the frontend
|
|
38
|
+
interface Message {
|
|
39
|
+
role: string
|
|
40
|
+
content: string
|
|
41
|
+
timestamp: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// take raw response text and render messages into the `#conversation` element
|
|
45
|
+
// Message timestamp is assumed to be a unique identifier of a message, and is used to deduplicate
|
|
46
|
+
// hence you can send data about the same message multiple times, and it will be updated
|
|
47
|
+
// instead of creating a new message elements
|
|
48
|
+
function addMessages(responseText: string) {
|
|
49
|
+
const lines = responseText.split('\n')
|
|
50
|
+
const messages: Message[] = lines.filter(line => line.length > 1).map(j => JSON.parse(j))
|
|
51
|
+
for (const message of messages) {
|
|
52
|
+
// we use the timestamp as a crude element id
|
|
53
|
+
const {timestamp, role, content} = message
|
|
54
|
+
const id = `msg-${timestamp}`
|
|
55
|
+
let msgDiv = document.getElementById(id)
|
|
56
|
+
if (!msgDiv) {
|
|
57
|
+
msgDiv = document.createElement('div')
|
|
58
|
+
msgDiv.id = id
|
|
59
|
+
msgDiv.title = `${role} at ${timestamp}`
|
|
60
|
+
msgDiv.classList.add('border-top', 'pt-2', role)
|
|
61
|
+
convElement.appendChild(msgDiv)
|
|
62
|
+
}
|
|
63
|
+
msgDiv.innerHTML = marked.parse(content)
|
|
64
|
+
}
|
|
65
|
+
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onError(error: any) {
|
|
69
|
+
console.error(error)
|
|
70
|
+
document.getElementById('error').classList.remove('d-none')
|
|
71
|
+
document.getElementById('spinner').classList.remove('active')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function onSubmit(e: SubmitEvent): Promise<void> {
|
|
75
|
+
e.preventDefault()
|
|
76
|
+
spinner.classList.add('active')
|
|
77
|
+
const body = new FormData(e.target as HTMLFormElement)
|
|
78
|
+
|
|
79
|
+
promptInput.value = ''
|
|
80
|
+
promptInput.disabled = true
|
|
81
|
+
|
|
82
|
+
const response = await fetch('/chat/', {method: 'POST', body})
|
|
83
|
+
await onFetchResponse(response)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// call onSubmit when the form is submitted (e.g. user clicks the send button or hits Enter)
|
|
87
|
+
document.querySelector('form').addEventListener('submit', (e) => onSubmit(e).catch(onError))
|
|
88
|
+
|
|
89
|
+
// load messages on page load
|
|
90
|
+
fetch('/chat/').then(onFetchResponse).catch(onError)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Simple example of using Pydantic AI to construct a Pydantic model from a text input.
|
|
2
|
+
|
|
3
|
+
Run with:
|
|
4
|
+
|
|
5
|
+
uv run -m pydantic_ai_examples.pydantic_model
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import cast
|
|
10
|
+
|
|
11
|
+
import logfire
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from pydantic_ai import Agent
|
|
15
|
+
from pydantic_ai.models import KnownModelName
|
|
16
|
+
|
|
17
|
+
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
|
|
18
|
+
logfire.configure(send_to_logfire='if-token-present')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MyModel(BaseModel):
|
|
22
|
+
city: str
|
|
23
|
+
country: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
model = cast(KnownModelName, os.getenv('PYDANTIC_AI_MODEL', 'openai:gpt-4o'))
|
|
27
|
+
print(f'Using model: {model}')
|
|
28
|
+
agent = Agent(model, result_type=MyModel)
|
|
29
|
+
|
|
30
|
+
if __name__ == '__main__':
|
|
31
|
+
result = agent.run_sync('The windy city in the US of A.')
|
|
32
|
+
print(result.data)
|
|
33
|
+
print(result.cost())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.version]
|
|
6
|
+
path = "../pyproject.toml"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "pydantic-ai-examples"
|
|
10
|
+
description = "Agent Framework / shim to use Pydantic with LLMs"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Samuel Colvin", email = "samuel@pydantic.dev" },
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Programming Language :: Python",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"Intended Audience :: Information Technology",
|
|
28
|
+
"Intended Audience :: System Administrators",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Operating System :: Unix",
|
|
31
|
+
"Operating System :: POSIX :: Linux",
|
|
32
|
+
"Environment :: Console",
|
|
33
|
+
"Environment :: MacOS X",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
"Topic :: Internet",
|
|
36
|
+
]
|
|
37
|
+
requires-python = ">=3.9"
|
|
38
|
+
dynamic = ["version"]
|
|
39
|
+
dependencies = [
|
|
40
|
+
"pydantic-ai-slim[openai,vertexai,groq]",
|
|
41
|
+
"asyncpg>=0.30.0",
|
|
42
|
+
"fastapi>=0.115.4",
|
|
43
|
+
"logfire[asyncpg,fastapi]>=2.3",
|
|
44
|
+
"python-multipart>=0.0.17",
|
|
45
|
+
"rich>=13.9.2",
|
|
46
|
+
"uvicorn>=0.32.0",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build]
|
|
50
|
+
include = ["*.py", "*.html", "*.ts"]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
53
|
+
"" = "pydantic_ai_examples"
|
|
54
|
+
|
|
55
|
+
[tool.uv.sources]
|
|
56
|
+
pydantic-ai-slim = { workspace = true }
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
extend = "../pyproject.toml"
|
|
60
|
+
line-length = 88
|