Skip to content
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
3 changes: 3 additions & 0 deletions python/.devcontainer/DockerFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.12-bullseye

RUN pip install hatch
21 changes: 21 additions & 0 deletions python/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/python
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
//"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
"dockerFile": "DockerFile",

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],


// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@
import json
import sys

import openai
import schema as coffeeshop
from dotenv import dotenv_values

from typechat import DefaultOpenAIModel, Failure, TypeChatTranslator, TypeChatValidator
from typechat import Failure, TypeChatTranslator, TypeChatValidator, create_language_model


async def main():
vals = dotenv_values()
client = openai.AsyncOpenAI(api_key=vals["OPENAI_API_KEY"])
model = DefaultOpenAIModel(model_name=vals.get("OPENAI_MODEL", None) or "gpt-3.5-turbo", client=client)
model = create_language_model(vals)
validator = TypeChatValidator(coffeeshop.Cart)
translator = TypeChatTranslator(model, validator, coffeeshop.Cart)
print("☕> ", end="", flush=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import Literal, NotRequired, TypedDict
from typing import Literal, NotRequired, TypedDict, Annotated
def Doc(s: str) -> str: return s


class UnknownText(TypedDict):
"""
Represents any text that could not be understood.
Use this type for order items that match nothing else
"""

type: Literal["UnknownText"]
text: str
text: Annotated[str, Doc("The text that wasn't understood")]


class Caffeine(TypedDict):
Expand Down Expand Up @@ -65,7 +66,7 @@ class Sweetener(TypedDict):

EspressoSize = Literal["solo", "doppio", "triple", "quad"]

OptionQuantity = Literal["no", "light", "regular", "extra"]
OptionQuantity = Literal["no", "light", "regular", "extra"] | int


class Syrup(TypedDict):
Expand All @@ -89,23 +90,23 @@ class LatteDrink(TypedDict):
type: Literal["LatteDrink"]
name: Literal["cappuccino", "flat white", "latte", "latte macchiato", "mocha", "chai latte"]
temperature: NotRequired["CoffeeTemperature"]
size: NotRequired["CoffeeSize"] # The default is 'grande'
size: NotRequired[Annotated[CoffeeSize, Doc("The default is 'grande'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


class EspressoDrink(TypedDict):
type: Literal["EspressoDrink"]
name: Literal["espresso", "lungo", "ristretto", "macchiato"]
temperature: NotRequired["CoffeeTemperature"]
size: NotRequired["EspressoSize"] # The default is 'doppio'
size: NotRequired[Annotated["EspressoSize", Doc("The default is 'doppio'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


class CoffeeDrink(TypedDict):
type: Literal["CoffeeDrink"]
name: Literal["americano", "coffee"]
temperature: NotRequired[CoffeeTemperature]
size: NotRequired[CoffeeSize] # The default is "grande"
size: NotRequired[Annotated[CoffeeSize, Doc("The default is 'grande'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


Expand All @@ -123,10 +124,10 @@ class BakeryPreparation(TypedDict):
class BakeryProduct(TypedDict):
type: Literal["BakeryProduct"]
name: Literal["apple bran muffin", "blueberry muffin", "lemon poppyseed muffin", "bagel"]
options: NotRequired[list[BakeryOption | BakeryPreparation]]
options: list[BakeryOption | BakeryPreparation]


Product = BakeryProduct | LatteDrink | CoffeeDrink | UnknownText
Product = BakeryProduct | LatteDrink | EspressoDrink | CoffeeDrink | UnknownText


class LineItem(TypedDict):
Expand All @@ -137,4 +138,4 @@ class LineItem(TypedDict):

class Cart(TypedDict):
type: Literal["Cart"]
items: list[LineItem | UnknownText]
items: list[LineItem | UnknownText]
3 changes: 3 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Source = "https://github.com/microsoft/TypeChat"
[tool.hatch.version]
path = "src/typechat/__about__.py"

[dirs.env]
virtual = ".hatch"

[tool.hatch.envs.default]
dependencies = [
"coverage[toml]>=6.5",
Expand Down
3 changes: 2 additions & 1 deletion python/src/typechat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: MIT

from typechat._internal.model import DefaultOpenAIModel, TypeChatModel
from typechat._internal.model import DefaultOpenAIModel, TypeChatModel, create_language_model
from typechat._internal.result import Failure, Result, Success
from typechat._internal.translator import TypeChatTranslator
from typechat._internal.ts_conversion import python_type_to_typescript_schema
Expand All @@ -17,4 +17,5 @@
"Failure",
"Result",
"python_type_to_typescript_schema",
"create_language_model",
]
20 changes: 19 additions & 1 deletion python/src/typechat/_internal/model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Protocol, override

import os
import openai

from typechat._internal.result import Failure, Result, Success
Expand Down Expand Up @@ -33,3 +33,21 @@ async def complete(self, input: str) -> Result[str]:
return Success(content)
except Exception as e:
return Failure(str(e))

def create_language_model(vals: dict[str,str|None]) -> TypeChatModel:
model:TypeChatModel
client: openai.AsyncOpenAI | openai.AsyncAzureOpenAI

if "OPENAI_API_KEY" in vals:
client = openai.AsyncOpenAI(api_key=vals["OPENAI_API_KEY"])
model = DefaultOpenAIModel(model_name=vals.get("OPENAI_MODEL", None) or "gpt-35-turbo", client=client)

elif "AZURE_OPENAI_API_KEY" in vals and "AZURE_OPENAI_ENDPOINT" in vals:
os.environ["OPENAI_API_TYPE"] = "azure"
client=openai.AsyncAzureOpenAI(azure_endpoint=vals.get("AZURE_OPENAI_ENDPOINT",None) or "", api_key=vals["AZURE_OPENAI_API_KEY"],api_version="2023-03-15-preview")
model = DefaultOpenAIModel(model_name=vals.get("AZURE_OPENAI_MODEL", None) or "gpt-35-turbo", client=client)

else:
raise ValueError("Missing environment variables for Open AI or Azure OpenAI model")

return model
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,24 @@ def convert_to_type_node(py_type: object) -> TypeNode:

def declare_property(name: str, py_annotation: type | TypeAliasType, optional: bool):
origin: object = py_annotation
comments: str = ""
while origin := get_origin(origin):
if origin is Annotated and hasattr(py_annotation, "__metadata__"):
comments = py_annotation.__metadata__[0]
elif origin in _KNOWN_GENERIC_SPECIAL_FORMS:
nested = get_args(py_annotation)
if nested:
nested_origin = get_origin(nested[0])
if nested_origin is Annotated:
comments = nested[0].__metadata__[0]
if origin is Required:
optional = False
break
if origin is NotRequired:
optional = True
break
type_annotation = convert_to_type_node(skip_annotations(py_annotation))
return PropertyDeclarationNode(name, optional, "", type_annotation)
return PropertyDeclarationNode(name, optional, comments, type_annotation)

def declare_type(py_type: object):
if is_typeddict(py_type):
Expand Down
13 changes: 7 additions & 6 deletions python/tests/coffeeshop.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Literal, NotRequired, TypedDict
from typing import Literal, NotRequired, TypedDict, Annotated

from typechat import python_type_to_typescript_schema
def Doc(s: str) -> str: return s


class UnknownText(TypedDict):
Expand All @@ -9,7 +10,7 @@ class UnknownText(TypedDict):
"""

type: Literal["UnknownText"]
text: str
text: Annotated[str, Doc("The text that wasn't understood")]


class Caffeine(TypedDict):
Expand Down Expand Up @@ -91,23 +92,23 @@ class LatteDrink(TypedDict):
type: Literal["LatteDrink"]
name: Literal["cappuccino", "flat white", "latte", "latte macchiato", "mocha", "chai latte"]
temperature: NotRequired["CoffeeTemperature"]
size: NotRequired["CoffeeSize"] # The default is 'grande'
size: NotRequired[Annotated["CoffeeSize", Doc("The default is 'grande'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


class EspressoDrink(TypedDict):
type: Literal["EspressoDrink"]
name: Literal["espresso", "lungo", "ristretto", "macchiato"]
temperature: NotRequired["CoffeeTemperature"]
size: NotRequired["EspressoSize"] # The default is 'doppio'
size: NotRequired[Annotated["EspressoSize", Doc("The default is 'doppio'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


class CoffeeDrink(TypedDict):
type: Literal["CoffeeDrink"]
name: Literal["americano", "coffee"]
temperature: NotRequired[CoffeeTemperature]
size: NotRequired[CoffeeSize] # The default is "grande"
size: NotRequired[Annotated[CoffeeSize, Doc("The default is 'grande'")]]
options: NotRequired[list[Creamer | Sweetener | Syrup | Topping | Caffeine | LattePreparation]]


Expand All @@ -128,7 +129,7 @@ class BakeryProduct(TypedDict):
options: NotRequired[list[BakeryOption | BakeryPreparation]]


Product = BakeryProduct | LatteDrink | CoffeeDrink | UnknownText
Product = BakeryProduct | LatteDrink | CoffeeDrink | EspressoDrink | UnknownText


class LineItem(TypedDict):
Expand Down