Skip to content

Commit

Permalink
Getting close to ontology generation
Browse files Browse the repository at this point in the history
  • Loading branch information
seanchatmangpt committed Feb 3, 2024
1 parent 88b496d commit d7c6367
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 27 deletions.
36 changes: 36 additions & 0 deletions src/rdddy/generators/assertion.log
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,39 @@ You will be penalized for not returning only a set for set_python_primitive_stri
2024-02-01 18:35:23,496 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for PydanticClassTemplateSpecificationModel
2024-02-01 18:36:14,720 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for PydanticClassTemplateSpecificationModel
2024-02-01 18:36:14,721 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for PydanticClassTemplateSpecificationModel
2024-02-02 14:39:54,969 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for MetaTemplateSpecificationBaseModel

Validation error:
malformed node or string on line 1: <ast.Name object at 0x1627059c0>
2024-02-02 14:39:54,971 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for MetaTemplateSpecificationBaseModel

Validation error:
malformed node or string on line 1: <ast.Name object at 0x162718b80>
2024-02-02 17:21:00,921 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a valid python list primitive type for
list_str_for_ast_literal_eval
You will be penalized for not returning only a list for list_str_for_ast_literal_eval
2024-02-02 17:28:34,365 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a valid python list primitive type for
list_str_for_ast_literal_eval
You will be penalized for not returning only a list for list_str_for_ast_literal_eval
2024-02-02 17:33:18,794 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for RootModel

Validation error:
malformed node or string on line 1: <ast.ListComp object at 0x15ce01750>
2024-02-02 17:33:18,795 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for RootModel

Validation error:
malformed node or string on line 1: <ast.ListComp object at 0x15cea1480>
2024-02-02 17:56:38,280 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for PydanticClassTemplateSpecificationModel

Validation error:
1 validation error for PydanticClassTemplateSpecificationModel
fields.3.default_value
Input should be a valid string [type=string_type, input_value=1, input_type=int]
For further information visit https://errors.pydantic.dev/2.5/v/string_type
2024-02-02 17:56:38,282 - dspy.primitives.assertions - ERROR - AssertionError: You need to create a kwargs dict for PydanticClassTemplateSpecificationModel

Validation error:
1 validation error for PydanticClassTemplateSpecificationModel
fields.3.default_value
Input should be a valid string [type=string_type, input_value=1, input_type=int]
For further information visit https://errors.pydantic.dev/2.5/v/string_type
9 changes: 9 additions & 0 deletions src/rdddy/generators/dsl_template_specification_base_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import Field
from rdddy.generators.gen_meta_template_spec_model import TemplateSpecificationBaseModel


class DSLTemplateSpecificationBaseModel(TemplateSpecificationBaseModel):
"""A Pydantic model for jinja templates that render DSL models."""
dsl_name = Field(default=None, description="The name of the DSL being defined")
dsl_description = Field(default=None, description="A detailed description of the DSL's purpose and usage.")
dsl_syntax = Field(default=None, description="The syntax of the DSL being defined")
175 changes: 175 additions & 0 deletions src/rdddy/generators/gen_meta_template_spec_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import dspy
import inflection
import jinja2

from rdddy.generators.gen_pydantic_instance import GenPydanticInstance
from rdddy.generators.gen_python_primitive import GenList
from typetemp.functional import render
from typing import List, Optional, Any

from pydantic import BaseModel, Field
from typing import List, Type


class TemplateSpecificationBaseModel(BaseModel):
"""Template specification base model."""
def __init__(self, **data: Any):
super().__init__(**data)


class TemplateSpecificationFieldBaseModel(BaseModel):
"""Template specification field model."""
def __init__(self, **data: Any):
super().__init__(**data)


class MetaTemplateSpecificationFieldModel(TemplateSpecificationFieldBaseModel):
"""A Pydantic model for jinja templates that render Pydantic fields."""
description: str = Field(
...,
description="A detailed description of the field's purpose and usage.",
)
field_name: str = Field(
...,
description="The name of the field in the generated template model. "
"It should fit the description and be unique. No prefixes, suffixes, or abbreviations.",
)
default_value: str | None = Field(
"...",
description="The default value for the field if not provided.",
)


class MetaTemplateSpecificationBaseModel(TemplateSpecificationBaseModel):
"""A Pydantic model for jinja templates that render BaseModels."""
class_name: str = Field(
...,
description="The name of the generated template model.",
)
docstring: str = Field(
...,
description="A detailed docstring of the model's purpose and usage.",
)
fields: List[MetaTemplateSpecificationFieldModel] = Field(
...,
description="A list of MetaFieldTemplateSpecificationModel instances defining the fields of the model.",
)


template_str = '''from pydantic import Field
from rdddy.generators.gen_meta_template_spec_model import TemplateSpecificationBaseModel
class {{ model.class_name }}(TemplateSpecificationBaseModel):
"""{{ model.docstring }}"""
{% for field in model.fields %}
{{ field.field_name }} = Field(default={{ field.default_value }}, description="{{ field.description }}")
{% endfor %}
'''


class RootModel(BaseModel):
root_model_class_name: str = Field(..., description="The name of the root model")
child_model_class_names: list[str] = Field(..., description="The names of the child models")


def gen_model_tree():
generate_answer = dspy.ChainOfThought("question -> answer")
prompt = """The iCalendar specification defines various entities that start with "V" for different components and properties. Here are some common entities that start with "V" in the iCalendar specification:
VEVENT: This is one of the most commonly used components in iCalendar and represents an event.
VTODO: Represents a to-do task or action item.
VJOURNAL: Represents a journal entry or a note.
VFREEBUSY: Represents information about the free or busy time of a calendar user.
VCALENDAR: The top-level component that encapsulates all other iCalendar components.
VTIMEZONE: Represents time zone information.
VAVAILABILITY: Represents availability information for a calendar user.
VALARM: Represents an alarm or reminder associated with an event or to-do.
Create the ICalendar root model and add the child entities
"""
model_module = GenPydanticInstance(root_model=RootModel)

icalendar_root_model = model_module.forward(prompt)

# for child in icalendar_root_model.children:


icalendar_entities = {
'VEVENT': 'This is one of the most commonly used components in iCalendar and represents an event.',
'VTODO': 'Represents a to-do task or action item.',
'VJOURNAL': 'Represents a journal entry or a note.',
'VFREEBUSY': 'Represents information about the free or busy time of a calendar user.',
'VTIMEZONE': 'Represents time zone information.',
'VAVAILABILITY': 'Represents availability information for a calendar user.',
'VALARM': 'Represents an alarm or reminder associated with an event or to-do.'
}


def generate_icalendar_models():
for entity, description in icalendar_entities.items():
generate_answer = dspy.ChainOfThought("question -> answer")
prompt = f"What are the exact fields or attributes of the {entity} in RFC 5545?"
answer = generate_answer(question=prompt).answer
print(f"{entity}: {answer}")

# Define a Pydantic class dynamically for each entity
model_prompt = f'I need a model named {entity}Model that has all of the relevant fields {description}'

model_module = GenPydanticInstance(root_model=MetaTemplateSpecificationBaseModel,
child_models=[MetaTemplateSpecificationFieldModel])

model_inst = model_module.forward(model_prompt)

print(model_inst)

# Render the Pydantic class from the specification
rendered_class_str = render_pydantic_class(model_inst, template_str)

# Write the rendered class to a Python file
write_pydantic_class_to_file(rendered_class_str, f"ical/{inflection.underscore(model_inst.class_name)}.py")

print(f"{model_inst.class_name} written to {model_inst.class_name}.py")


def render_pydantic_class(model_spec, template_str):
template = jinja2.Template(template_str)
return template.render(model=model_spec)


def write_pydantic_class_to_file(class_str, filename):
with open(filename, 'w') as file:
file.write(class_str)


# Example usage
def main():
model_prompt = """I need a class named DSLTemplateSpecificationBaseModel,
so that I can create Pydantic models for Domain Specific Languages. Come up with
field names that fit a DSL template."""

model_module = GenPydanticInstance(root_model=MetaTemplateSpecificationBaseModel,
child_models=[MetaTemplateSpecificationFieldModel])

model_inst = model_module.forward(model_prompt)

# Render the Pydantic class from the specification
rendered_class_str = render(template_str, model=model_inst)

# Write the rendered class to a Python file
write_pydantic_class_to_file(rendered_class_str, f"{inflection.underscore(model_inst.class_name)}.py")


if __name__ == '__main__':
lm = dspy.OpenAI(max_tokens=1000)
dspy.settings.configure(lm=lm)
# main()
generate_icalendar_models()
61 changes: 46 additions & 15 deletions src/rdddy/generators/gen_pydantic_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,22 @@
class FieldTemplateSpecificationModel(BaseModel):
field_name: str = Field(
...,
title="Field Name",
description="The name of the field in the model.",
description="The name of the field in the model. PEP8 naming. No prefixes, suffixes, or abbreviations.",
)
field_type: str = Field(
...,
title="Field Type",
description="The data type of the field, e.g., 'str', 'int', 'EmailStr', or 'datetime'.",
description="The data type of the field, e.g., 'str', 'int', 'EmailStr', or 'datetime'. No dict or classes.",
)
default_value: str | None = Field(
"...",
title="Default Value",
description="The default value for the field if not provided.",
)
description: str = Field(
...,
title="Description",
description="A detailed description of the field's purpose and usage.",
)
constraints: str | None = Field(
None,
title="Constraints",
description="Constraints or validation rules for the field, if any. Specify as a string, e.g., 'min_length=2, max_length=50' or 'ge=0, le=120'.",
)

Expand Down Expand Up @@ -84,22 +79,18 @@ class ValidatorTemplateSpecificationModel(BaseModel):
class PydanticClassTemplateSpecificationModel(BaseModel):
class_name: str = Field(
...,
title="Model Class Name",
description="The class name of the Pydantic model.",
)
description: str = Field(
...,
title="Description",
description="A detailed description of the Pydantic model's purpose and usage.",
)
fields: List[FieldTemplateSpecificationModel] = Field(
...,
title="Fields",
description="A list of field specifications for the model. Each field specifies the name, type, default value, description, and constraints.",
description="A list of field specifications for the model. Each field specifies the name, type, default value, description, and constraints. 10 fields max.",
)



template_str = '''from pydantic import BaseModel, Field, validator, root_validator, EmailStr, UrlStr
from typing import List, Optional
from datetime import datetime
Expand Down Expand Up @@ -127,6 +118,7 @@ class Config:
{% endif %}
'''


def render_pydantic_class(model_spec, template_str):
template = jinja2.Template(template_str)
return template.render(model=model_spec)
Expand All @@ -145,8 +137,7 @@ def main():
model_prompt = "I need a verbose contact model named ContactModel from the friend of a friend ontology with 10 fields, each with length constraints"

model_module = GenPydanticInstance(root_model=PydanticClassTemplateSpecificationModel,
child_models=[FieldTemplateSpecificationModel
])
child_models=[FieldTemplateSpecificationModel])

model_inst = model_module.forward(model_prompt)

Expand All @@ -157,5 +148,45 @@ def main():
write_pydantic_class_to_file(rendered_class_str, f"{inflection.underscore(model_inst.class_name)}.py")


icalendar_entities = {
'VEVENT': 'This is one of the most commonly used components in iCalendar and represents an event.',
'VTODO': 'Represents a to-do task or action item.',
'VJOURNAL': 'Represents a journal entry or a note.',
'VFREEBUSY': 'Represents information about the free or busy time of a calendar user.',
'VTIMEZONE': 'Represents time zone information.',
'VAVAILABILITY': 'Represents availability information for a calendar user.',
'VALARM': 'Represents an alarm or reminder associated with an event or to-do.'
}


def generate_icalendar_models():
for entity, description in icalendar_entities.items():
# generate_answer = dspy.ChainOfThought("question -> answer")
# prompt = f"What are the exact fields or attributes of the {entity} in RFC 5545?"
# answer = generate_answer(question=prompt).answer
# print(f"{entity}: {answer}")

# Define a Pydantic class dynamically for each entity
model_prompt = f'I need a model named {entity}Model that has all of the relevant fields for RFC 5545 compliance.'

model_module = GenPydanticInstance(root_model=PydanticClassTemplateSpecificationModel,
child_models=[FieldTemplateSpecificationModel])

model_inst = model_module.forward(model_prompt)

print(model_inst)

# Render the Pydantic class from the specification
rendered_class_str = render_pydantic_class(model_inst, template_str)

# Write the rendered class to a Python file
write_pydantic_class_to_file(rendered_class_str, f"ical/{inflection.underscore(model_inst.class_name)}.py")

print(f"{model_inst.class_name} written to {model_inst.class_name}.py")

if __name__ == '__main__':
main()
lm = dspy.OpenAI(max_tokens=3000)
dspy.settings.configure(lm=lm)

generate_icalendar_models()
# main()
2 changes: 1 addition & 1 deletion src/rdddy/generators/gen_python_primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, primitive_type, lm=None):
raise ValueError(
f"primitive type {primitive_type.__name__} must be a Python primitive type"
)
super().__init__(f"{primitive_type.__name__}_python_primitive_pep8_string", lm)
super().__init__(f"{primitive_type.__name__}_str_for_ast_literal_eval", lm)
self.primitive_type = primitive_type

def validate_primitive(self, output) -> bool:
Expand Down
8 changes: 5 additions & 3 deletions src/rdddy/generators/gen_signature.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import dspy
from jinja2 import Template
import os

from rdddy.generators.gen_pydantic_instance import GenPydanticInstance
from rdddy.signature_factory import *
from rdddy.signature_factory import SignatureTemplateSpecModel, InputFieldTemplateSpecModel, OutputFieldTemplateSpecModel


def render_signature_class(model, template_str):
Expand Down Expand Up @@ -39,13 +40,14 @@ def main():

sig_prompt = "I need a signature called GenJinjaSignature that allows input of 'source', and output 'jinja_template'. The signature needs to create proper jinja templates from the source"

sig_module = GenPydanticInstance(root_model=SignatureModel,
child_models=[InputFieldModel, OutputFieldModel])
sig_module = GenPydanticInstance(root_model=SignatureTemplateSpecModel,
child_models=[InputFieldTemplateSpecModel, OutputFieldTemplateSpecModel])

sig_inst = sig_module.forward(sig_prompt)

rendered_class_str = render_signature_class(sig_inst, template_str)
write_signature_class_to_file(rendered_class_str, 'output_signature.py')


if __name__ == '__main__':
main()
Loading

0 comments on commit d7c6367

Please sign in to comment.