Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Pollak committed Aug 4, 2024
1 parent 168e106 commit fddedc0
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 85 deletions.
147 changes: 129 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ has a list of discriminated unions, shown by `pet_type`. For each object
the model is required to return different things.

You should be able to use the full power of Pydantic here. I’ve found
that instructor for Claude fails on this example
that instructor for Claude fails on this example.

Each sub BaseModel may also have docstrings describing usage. I’ve found
prompting this way to be quite reliable.

``` python
class Cat(BaseModel):
Expand All @@ -81,25 +84,25 @@ class Reptile(BaseModel):
pet_type: Literal['lizard', 'dragon']
scales: bool

# Dummy to show doc strings
class Create(BaseModel):
"Pass as final member of the `pet` list to indicate success"
pet_type: Literal['create']

class OwnersPets(BaseModel):
"""
Information for to gather for an Owner's pets
"""
pet: List[Union[Cat, Dog, Reptile]] = Field(..., discriminator='pet_type')
pet: List[Union[Cat, Dog, Reptile, Create]] = Field(..., discriminator='pet_type')

chat = Chat(model)
pr = "hello I am a new owner and I would like to add some pets for me. I have a dog which has 6 barks, a dragon with no scales, and a cat with 2 meows"
chat.struct(OwnersPets, pr=pr)
```

OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2)])

``` python
chat.struct(OwnersPets, pr="actually my dragon does have scales, can you change that for me?")
print(repr(chat.struct(OwnersPets, pr=pr)))
print(repr(chat.struct(OwnersPets, pr="actually my dragon does have scales, can you change that for me?")))
```

OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=True), Cat(pet_type='cat', meows=2)])
OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2), Create(pet_type='create')])
OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=True), Cat(pet_type='cat', meows=2), Create(pet_type='create')])

While the struct uses tool use to enforce the schema, we save in history
as the `repr` response to keep the user,assistant,user flow.
Expand All @@ -113,13 +116,13 @@ chat.h
'text': 'hello I am a new owner and I would like to add some pets for me. I have a dog which has 6 barks, a dragon with no scales, and a cat with 2 meows'}]},
{'role': 'assistant',
'content': [{'type': 'text',
'text': "OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2)])"}]},
'text': "OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2), Create(pet_type='create')])"}]},
{'role': 'user',
'content': [{'type': 'text',
'text': 'actually my dragon does have scales, can you change that for me?'}]},
{'role': 'assistant',
'content': [{'type': 'text',
'text': "OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=True), Cat(pet_type='cat', meows=2)])"}]}]
'text': "OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=True), Cat(pet_type='cat', meows=2), Create(pet_type='create')])"}]}]

Alternatively you can use struct as tool use flow with
`treat_as_output=False` (but requires the next input to be assistant)
Expand All @@ -133,11 +136,11 @@ chat.h[-3:]
'content': [{'type': 'text',
'text': 'hello I am a new owner and I would like to add some pets for me. I have a dog which has 6 barks, a dragon with no scales, and a cat with 2 meows'}]},
{'role': 'assistant',
'content': [ToolUseBlock(id='toolu_01QwvZBqPf5kwQX6vGUHD7tE', input={'pet': [{'pet_type': 'dog', 'barks': 6}, {'pet_type': 'dragon', 'scales': False}, {'pet_type': 'cat', 'meows': 2}]}, name='OwnersPets', type='tool_use')]},
'content': [ToolUseBlock(id='toolu_015ggQ1iH6BxBffd7erj3rjR', input={'pet': [{'pet_type': 'dog', 'barks': 6.0}, {'pet_type': 'dragon', 'scales': False}, {'pet_type': 'cat', 'meows': 2}]}, name='OwnersPets', type='tool_use')]},
{'role': 'user',
'content': [{'type': 'tool_result',
'tool_use_id': 'toolu_01QwvZBqPf5kwQX6vGUHD7tE',
'content': "pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2)]"}]}]
'tool_use_id': 'toolu_015ggQ1iH6BxBffd7erj3rjR',
'content': "OwnersPets(pet=[Dog(pet_type='dog', barks=6.0), Reptile(pet_type='dragon', scales=False), Cat(pet_type='cat', meows=2)])"}]}]

(So I couldn’t prompt it again here, next input would have to be an
assistant)
Expand All @@ -158,11 +161,10 @@ class User(BaseModel):
'examples': ['Monkey!123'],
}
)
res = c.struct(msgs=["Can you create me a new user for tom age 22"], resp_model=User, sp="for a given user, generate a similar password based on examples")
print(res)
print(repr(c.struct(msgs=["Can you create me a new user for tom age 22"], resp_model=User, sp="for a given user, generate a similar password based on examples")))
```

age=22 name='tom' password='Monkey!123'
User(age=22, name='tom', password='Monkey!123')

Uses the few-shot example as asked for in the system prompt.

Expand All @@ -185,3 +187,112 @@ Chat.struct(
**, # Chat.__call__ kwargs...
) -> BaseModel
```

``` python
from pydantic import BaseModel, Field
from typing import Literal, List

class Fish(BaseModel):
"""
Represent a fish for sale. When suggesting a fish:
- Highlight its unique features and care requirements
- Mention its personality traits
- Suggest a name from the examples, as if you're naming it for the customer
"""
resp_type: Literal['fish']
species: str
name: str = Field(
description="Suggested name for the fish",
json_schema_extra={
"examples": ["Bubbles", "Finn", "Nemo", "Splash", "Goldie"]
}
)
tank_size: float = Field(description="Recommended tank size in gallons")
temperament: str = Field(description="Fish's general behavior and compatibility")
lifespan: str = Field(description="Expected lifespan of the fish")

class CustomerResponse(BaseModel):
"""
Represent the customer's response. When speaking as the customer:
- Express interest or concerns about the suggested fish
- Ask questions about care, feeding, or compatibility
- Show enthusiasm or hesitation based on the information provided
"""
resp_type: Literal['customer']
response: str

class SalespersonResponse(BaseModel):
"""
Represent the salesperson's response. When speaking as the salesperson:
- Address the customer's questions or concerns
- Provide additional information about fish care
- Suggest accessories or additional fish that might be compatible
- Use a friendly and informative tone to build trust
"""
resp_type: Literal['sales']
response: str

class FishStoreConversation(BaseModel):
"""
Simulate a conversation between a customer and a salesperson in a fish store.
The conversation should flow naturally, with the salesperson suggesting fish
and the customer responding. Use the Fish, CustomerResponse, and SalespersonResponse
models alternately to create a realistic dialogue.
"""
conversation: List[Union[Fish, CustomerResponse, SalespersonResponse]] = Field(..., discriminator='resp_type')
```

``` python
from claudette.core import *
import claudette_pydantic

model = models[-1] # Use the latest model
chat = Chat(model)

# Start the conversation
initial_prompt = "I'm interested in buying a fish for my new apartment. Can you help me choose one?"

result = chat.struct(
FishStoreConversation,
pr=initial_prompt,
sp="Simulate a conversation between a fish store salesperson and a customer. The salesperson should suggest a fish, and the conversation should continue for a few exchanges. Use the docstrings to guide the conversation flow and content."
)

print(repr(result))
```

FishStoreConversation(conversation=[Fish(resp_type='fish', species='Betta Fish', name='Bubbles', tank_size=5.0, temperament='Peaceful but territorial, does well in a small tank on its own', lifespan='2-5 years'), CustomerResponse(resp_type='customer', response="Ooh, a Betta fish sounds interesting! I've heard they have beautiful fins. How much space do they need and what kind of care do they require?"), SalespersonResponse(resp_type='sales', response="Betta fish are a great choice for a small apartment! They only need a 5 gallon tank, which is perfect for your space. They're relatively low maintenance - just need their water changed a couple times a week and some fish flakes to eat. Their long, flowing fins make them very eye-catching. And they have such fun personalities, they can be quite interactive with their owners. What do you think, does a Betta sound like a good fit?"), CustomerResponse(resp_type='customer', response='That sounds perfect! I love the idea of having a little fish friend to keep me company. A 5 gallon tank is manageable for my apartment. Do Bettas get along with other fish or do they need to be on their own?'), SalespersonResponse(resp_type='sales', response="Bettas are generally best kept on their own. They can be quite territorial and may fight with other fish, especially those with long, flowing fins like themselves. I'd recommend setting up a nice 5 gallon tank just for your Betta. You could add some live plants or decorations for them to explore, but no tank mates. That way you can really focus on giving your Betta the best care and attention. Does that sound like a good plan?")])

``` python
import json
print(json.dumps(json.loads(result.json()), indent=2))
```

{
"conversation": [
{
"resp_type": "fish",
"species": "Betta Fish",
"name": "Bubbles",
"tank_size": 5.0,
"temperament": "Peaceful but territorial, does well in a small tank on its own",
"lifespan": "2-5 years"
},
{
"resp_type": "customer",
"response": "Ooh, a Betta fish sounds interesting! I've heard they have beautiful fins. How much space do they need and what kind of care do they require?"
},
{
"resp_type": "sales",
"response": "Betta fish are a great choice for a small apartment! They only need a 5 gallon tank, which is perfect for your space. They're relatively low maintenance - just need their water changed a couple times a week and some fish flakes to eat. Their long, flowing fins make them very eye-catching. And they have such fun personalities, they can be quite interactive with their owners. What do you think, does a Betta sound like a good fit?"
},
{
"resp_type": "customer",
"response": "That sounds perfect! I love the idea of having a little fish friend to keep me company. A 5 gallon tank is manageable for my apartment. Do Bettas get along with other fish or do they need to be on their own?"
},
{
"resp_type": "sales",
"response": "Bettas are generally best kept on their own. They can be quite territorial and may fight with other fish, especially those with long, flowing fins like themselves. I'd recommend setting up a nice 5 gallon tank just for your Betta. You could add some live plants or decorations for them to explore, but no tank mates. That way you can really focus on giving your Betta the best care and attention. Does that sound like a good plan?"
}
]
}
Loading

0 comments on commit fddedc0

Please sign in to comment.