[docs]classSimpleModel(BaseModel,Generic[T]):"""Simple model for a single item."""item:T
[docs]classPropertySettings(BaseModel):"""Property settings for a prompty model."""model_config=ConfigDict(arbitrary_types_allowed=True)type:Literal["string","number","array","object","boolean"]default:Union[str,int,float,List,Dict,bool,None]=Field(default=None)description:str=Field(default="")
[docs]classModelSettings(BaseModel):"""Model settings for a prompty model."""api:str=Field(default="")configuration:dict=Field(default={})parameters:dict=Field(default={})response:dict=Field(default={})
[docs]defto_safe_dict(self)->Dict[str,Any]:d={}fork,vinself:ifv!=""andv!={}andv!=[]andvisnotNone:ifk=="model":d[k]=v.model_dump_safe()elifk=="template":d[k]=v.model_dump()elifk=="inputs"ork=="outputs":d[k]={k:v.model_dump()fork,vinv.items()}elifk=="file":d[k]=(str(self.file.as_posix())ifisinstance(self.file,Path)elseself.file)elifk=="basePrompty":# no need to serialize basePromptycontinueelse:d[k]=vreturnd
[docs]@staticmethoddefnormalize(attribute:Any,parent:Path,env_error:bool=True)->Any:ifisinstance(attribute,str):attribute=attribute.strip()ifattribute.startswith("${")andattribute.endswith("}"):variable=attribute[2:-1].split(":")ifvariable[0]inos.environ.keys():returnos.environ[variable[0]]else:iflen(variable)>1:returnvariable[1]else:ifenv_error:raiseValueError(f"Variable {variable[0]} not found in environment")else:return""elif(attribute.startswith("file:")andPath(parent/attribute.split(":")[1]).exists()):withopen(parent/attribute.split(":")[1],"r")asf:items=json.load(f)ifisinstance(items,list):return[Prompty.normalize(value,parent)forvalueinitems]elifisinstance(items,dict):return{key:Prompty.normalize(value,parent)forkey,valueinitems.items()}else:returnitemselse:returnattributeelifisinstance(attribute,list):return[Prompty.normalize(value,parent)forvalueinattribute]elifisinstance(attribute,dict):return{key:Prompty.normalize(value,parent)forkey,valueinattribute.items()}else:returnattribute
[docs]defparam_hoisting(top:Dict[str,Any],bottom:Dict[str,Any],top_key:Any=None)->Dict[str,Any]:"""Merge two dictionaries with hoisting of parameters from bottom to top. Args: top: The top dictionary. bottom: The bottom dictionary. top_key: The key to hoist from the bottom to the top. Returns: The merged dictionary. """iftop_key:new_dict={**top[top_key]}iftop_keyintopelse{}else:new_dict={**top}forkey,valueinbottom.items():ifkeynotinnew_dict:new_dict[key]=valuereturnnew_dict
[docs]classInvoker(abc.ABC):"""Base class for all invokers."""
[docs]classInvokerFactory(object):"""Factory for creating invokers."""_instance=None_renderers:Dict[str,Type[Invoker]]={}_parsers:Dict[str,Type[Invoker]]={}_executors:Dict[str,Type[Invoker]]={}_processors:Dict[str,Type[Invoker]]={}def__new__(cls)->InvokerFactory:ifcls._instanceisNone:cls._instance=super(InvokerFactory,cls).__new__(cls)# Add NOOP invokerscls._renderers["NOOP"]=NoOpParsercls._parsers["NOOP"]=NoOpParsercls._executors["NOOP"]=NoOpParsercls._processors["NOOP"]=NoOpParserreturncls._instance
[docs]defregister(self,type:Literal["renderer","parser","executor","processor"],name:str,invoker:Type[Invoker],)->None:iftype=="renderer":self._renderers[name]=invokereliftype=="parser":self._parsers[name]=invokereliftype=="executor":self._executors[name]=invokereliftype=="processor":self._processors[name]=invokerelse:raiseValueError(f"Invalid type {type}")
def__call__(self,type:Literal["renderer","parser","executor","processor"],name:str,prompty:Prompty,data:BaseModel,)->Any:iftype=="renderer":returnself._renderers[name](prompty)(data)eliftype=="parser":returnself._parsers[name](prompty)(data)eliftype=="executor":returnself._executors[name](prompty)(data)eliftype=="processor":returnself._processors[name](prompty)(data)else:raiseValueError(f"Invalid type {type}")
[docs]classFrontmatter:"""Class for reading frontmatter from a string or file."""_yaml_delim=r"(?:---|\+\+\+)"_yaml=r"(.*?)"_content=r"\s*(.+)$"_re_pattern=r"^\s*"+_yaml_delim+_yaml+_yaml_delim+_content_regex=re.compile(_re_pattern,re.S|re.M)
[docs]@classmethoddefread_file(cls,path:str)->dict[str,Any]:"""Reads file at path and returns dict with separated frontmatter. See read() for more info on dict return value. """withopen(path,encoding="utf-8")asfile:file_contents=file.read()returncls.read(file_contents)
[docs]@classmethoddefread(cls,string:str)->dict[str,Any]:"""Returns dict with separated frontmatter from string. Returned dict keys: attributes -- extracted YAML attributes in dict form. body -- string contents below the YAML separators frontmatter -- string representation of YAML """fmatter=""body=""result=cls._regex.search(string)ifresult:fmatter=result.group(1)body=result.group(2)return{"attributes":yaml.load(fmatter,Loader=yaml.FullLoader),"body":body,"frontmatter":fmatter,}