Skip to content

Commit

Permalink
Merge pull request #312 from FullStackWithLawrence/next
Browse files Browse the repository at this point in the history
add selector key
  • Loading branch information
lpm0073 authored Jan 29, 2024
2 parents 3a85da5 + 65adb8b commit 89231de
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 76 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.coverage
.coverage.*
htmlcov

# Jupyter Notebook
.ipynb_checkpoints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,16 @@ def handler(event, context):
for plugin in plugins:
if search_terms_are_in_messages(
messages=messages,
search_terms=plugin.prompting.search_terms.strings,
search_pairs=plugin.prompting.search_terms.pairs,
search_terms=plugin.selector.search_terms.strings,
search_pairs=plugin.selector.search_terms.pairs,
):
model = "gpt-3.5-turbo-1106"
messages = customized_prompt(plugin=plugin, messages=messages)
custom_tool = plugin_tool_factory(plugin=plugin)
tools.append(custom_tool)
print(f"Adding plugin: {plugin.name} {plugin.meta_data.version} created by {plugin.meta_data.author}")
print(
f"Adding plugin: {plugin.name} {plugin.meta_data.plugin_version} created by {plugin.meta_data.plugin_author}"
)

# https://platform.openai.com/docs/guides/gpt/chat-completions-api
validate_item(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

log = logging.getLogger(__name__)
CONFIG_PATH = PYTHON_ROOT + "/openai_api/lambda_openai_function/plugins/"
VALID_PLUGIN_VERSIONS = ["0.1.0"]
VALID_DIRECTIVES = ["search_terms", "always_load"]


def do_error(class_name: str, err: str) -> None:
Expand All @@ -31,7 +33,7 @@ def validate_required_keys(class_name: str, required_keys: list, plugin_json: di
"""Validate the required keys"""
for key in required_keys:
if key not in plugin_json:
do_error(class_name, err=f"Invalid search_terms: {plugin_json}. Missing key: {key}.")
do_error(class_name, err=f"Invalid {class_name}: {plugin_json}. Missing key: {key}.")


class PluginBase(BaseModel):
Expand Down Expand Up @@ -74,7 +76,7 @@ def to_json(self) -> json:
class SearchTerms(PluginBase):
"""Search terms of a Plugin object"""

plugin_json: dict = Field(..., description="Config object")
plugin_json: dict = Field(..., description="Plugin object")

@field_validator("plugin_json")
@classmethod
Expand Down Expand Up @@ -120,7 +122,7 @@ def to_json(self) -> json:
class AdditionalInformation(PluginBase):
"""Additional information of a Plugin object"""

plugin_json: dict = Field(..., description="Config object")
plugin_json: dict = Field(..., description="Plugin object")

@field_validator("plugin_json")
@classmethod
Expand All @@ -143,8 +145,7 @@ def to_json(self) -> json:
class Prompting(PluginBase):
"""Prompting child class of a Plugin object"""

plugin_json: dict = Field(..., description="Config object")
search_terms: SearchTerms = Field(None, description="Search terms of the plugin object")
plugin_json: dict = Field(..., description="Plugin object")
system_prompt: SystemPrompt = Field(None, description="System prompt of the plugin object")

@root_validator(pre=True)
Expand All @@ -154,29 +155,31 @@ def set_fields(cls, values):
if not isinstance(plugin_json, dict):
raise ValueError(f"Expected plugin_json to be a dict but received {type(plugin_json)}")
if plugin_json:
values["search_terms"] = SearchTerms(plugin_json=plugin_json["search_terms"])
values["system_prompt"] = SystemPrompt(system_prompt=plugin_json["system_prompt"])
return values

@field_validator("plugin_json")
@classmethod
def validate_plugin_json(cls, plugin_json) -> dict:
"""Validate the plugin object"""
required_keys = ["search_terms", "system_prompt"]
required_keys = ["system_prompt"]
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
return plugin_json

@property
def system_prompt(self) -> str:
"""Return the system prompt"""
return self.plugin_json.get("system_prompt")

def to_json(self) -> json:
"""Return the plugin as a JSON object"""
return {
"search_terms": self.search_terms.to_json(),
"system_prompt": self.system_prompt.system_prompt,
}
return self.plugin_json


class FunctionCalling(PluginBase):
"""FunctionCalling child class of a Plugin"""

plugin_json: dict = Field(..., description="Config object")
plugin_json: dict = Field(..., description="Plugin object")
function_description: str = Field(None, description="Description of the function")
additional_information: AdditionalInformation = Field(None, description="Additional information of the function")

Expand Down Expand Up @@ -222,7 +225,7 @@ def to_json(self) -> json:
class MetaData(PluginBase):
"""Metadata of a Plugin object"""

plugin_json: dict = Field(..., description="Config object")
plugin_json: dict = Field(..., description="Plugin object")

@root_validator(pre=True)
def set_fields(cls, values):
Expand All @@ -239,53 +242,106 @@ def validate_plugin_json(cls, plugin_json) -> dict:
if not isinstance(plugin_json, dict):
do_error(class_name=cls.__name__, err=f"Expected a dict but received {type(plugin_json)}")

required_keys = ["plugin_path", "name", "description", "version", "author"]
required_keys = ["plugin_name", "plugin_description", "plugin_version", "plugin_author"]
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
if str(plugin_json["plugin_version"]) not in VALID_PLUGIN_VERSIONS:
do_error(
class_name=cls.__name__,
err=f"Invalid plugin object: {plugin_json}. 'plugin_version' should be one of {VALID_PLUGIN_VERSIONS}.",
)
return plugin_json

@property
def name(self) -> str:
def plugin_name(self) -> str:
"""Return the name of the plugin object"""
return self.plugin_json.get("name") if self.plugin_json else None
return self.plugin_json.get("plugin_name") if self.plugin_json else None

@property
def plugin_path(self) -> str:
"""Return the path of the plugin object"""
return self.plugin_json.get("plugin_path") if self.plugin_json else None

@property
def description(self) -> str:
def plugin_description(self) -> str:
"""Return the description of the plugin object"""
return self.plugin_json.get("description") if self.plugin_json else None
return self.plugin_json.get("plugin_description") if self.plugin_json else None

@property
def version(self) -> str:
def plugin_version(self) -> str:
"""Return the version of the plugin object"""
return self.plugin_json.get("version") if self.plugin_json else None
return self.plugin_json.get("plugin_version") if self.plugin_json else None

@property
def author(self) -> str:
def plugin_author(self) -> str:
"""Return the author of the plugin object"""
return self.plugin_json.get("author") if self.plugin_json else None
return self.plugin_json.get("plugin_author") if self.plugin_json else None

def to_json(self) -> json:
"""Return the plugin as a JSON object"""
return self.plugin_json


class Selector(PluginBase):
"""Selector of a Plugin object"""

plugin_json: dict = Field(..., description="Plugin object")
directive: str = Field(None, description="Directive of the Selector object")
search_terms: SearchTerms = Field(None, description="Search terms of the Selector object")

@root_validator(pre=True)
def set_fields(cls, values):
"""proxy for __init__() - Set the fields"""
plugin_json = values.get("plugin_json")

if not isinstance(plugin_json, dict):
raise ValueError(f"Expected plugin_json to be a dict but received {type(plugin_json)}")
if plugin_json:
values["directive"] = plugin_json["directive"]
values["search_terms"] = SearchTerms(plugin_json=plugin_json["search_terms"])
return values

@field_validator("plugin_json")
@classmethod
def validate_plugin_json(cls, plugin_json) -> dict:
"""Validate the plugin object"""
required_keys = ["directive", "search_terms"]
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
if not isinstance(plugin_json["directive"], str):
do_error(
class_name=cls.__name__,
err=f"Invalid plugin object: {plugin_json}. 'directive' should be a string.",
)

@field_validator("directive")
@classmethod
def validate_directive(cls, directive) -> dict:
"""Validate the plugin object"""
if directive not in VALID_DIRECTIVES:
do_error(
class_name=cls.__name__,
err=f"Invalid plugin object: {directive}. 'directive' should be one of {VALID_DIRECTIVES}.",
)
return directive

def to_json(self) -> json:
"""Return the plugin as a JSON object"""
return {
"directive": self.directive,
"search_terms": self.search_terms.to_json(),
}


class Plugin(PluginBase):
"""A json object that contains the plugin for a plugin.function_calling_plugin() function"""

index: int = Field(0, description="Index of the plugin object")
plugin_json: dict = Field(..., description="Config object")
plugin_json: dict = Field(..., description="Plugin object")

# Child classes
meta_data: Optional[MetaData] = Field(None, description="Metadata of the plugin object")
selector: Optional[Selector] = Field(None, description="Selector of the plugin object")
prompting: Optional[Prompting] = Field(None, description="Prompting of the plugin object")
function_calling: Optional[FunctionCalling] = Field(None, description="FunctionCalling of the plugin object")

@property
def name(self) -> str:
"""Return a name in the format: "WillyWonka"""
return self.meta_data.name
return self.meta_data.plugin_name

@root_validator(pre=True)
def set_fields(cls, values):
Expand All @@ -295,6 +351,7 @@ def set_fields(cls, values):
raise ValueError(f"Expected plugin_json to be a dict but received {type(plugin_json)}")
if plugin_json:
values["meta_data"] = MetaData(plugin_json=plugin_json.get("meta_data"))
values["selector"] = Selector(plugin_json=plugin_json.get("selector"))
values["prompting"] = Prompting(plugin_json=plugin_json.get("prompting"))
values["function_calling"] = FunctionCalling(plugin_json=plugin_json.get("function_calling"))
return values
Expand All @@ -304,7 +361,7 @@ def set_fields(cls, values):
def validate_plugin_json(cls, plugin_json) -> None:
"""Validate the plugin object"""

required_keys = ["meta_data", "prompting", "function_calling"]
required_keys = ["meta_data", "selector", "prompting", "function_calling"]
for key in required_keys:
if key not in plugin_json:
cls.do_error(f"Invalid plugin object: {plugin_json}. Missing key: {key}.")
Expand All @@ -319,6 +376,7 @@ def to_json(self) -> json:
return {
"name": self.name,
"meta_data": self.meta_data.to_json(),
"selector": self.selector.to_json(),
"prompting": self.prompting.to_json(),
"function_calling": self.function_calling.to_json(),
}
Expand Down Expand Up @@ -363,7 +421,7 @@ def __init__(self, plugin_path: str = None, aws_s3_bucket_name: str = None):
plugin = Plugin(plugin_json=plugin_json, index=i)
self._custom_plugins.append(plugin)
print(
f"Loaded plugin from AWS S3 bucket: {plugin.name} {plugin.meta_data.version} created by {plugin.meta_data.author}"
f"Loaded plugin from AWS S3 bucket: {plugin.name} {plugin.meta_data.plugin_version} created by {plugin.meta_data.plugin_author}"
)

@property
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
meta_data:
plugin_path: aws_openai/lambda_openai_function/custom_configs/everlasting-gobstopper.yaml
# The name of your chatbot.
name: EverlastingGobstopper
plugin_name: EverlastingGobstopper
# The description of your chatbot.
description: Get additional information about the Everlasting Gobstopper product created by Willy Wonka Chocolate Factory. Information includes sales promotions, coupon codes, company contact information and biographical background on the company founder.
plugin_description: Get additional information about the Everlasting Gobstopper product created by Willy Wonka Chocolate Factory. Information includes sales promotions, coupon codes, company contact information and biographical background on the company founder.
# The version of your chatbot.
version: 0.1.0
plugin_version: 0.1.0
# The author of your chatbot.
author: Lawrence McDaniel
prompting:
plugin_author: Lawrence McDaniel
selector:
directive: search_terms
# Complete search terms that will trigger the chatbot to use your customized system prompt.
search_terms:
strings:
Expand All @@ -22,6 +22,7 @@ prompting:
- gobstopper
- - everlasting
- gobstoppers
prompting:
system_prompt: >
You are a helpful marketing agent for the [Willy Wonka Chocolate Factory](https://wwcf.com).
function_calling:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
# Each of these fields is required.
# ------------------------------------------------------------
meta_data:
plugin_path: aws_openai/lambda_openai_function/custom_configs/example-configuration.yaml
name: ExampleConfiguration
description: A 'hello world' style plugin. This is an example plugin to integrate with OpenAI API Function Calling additional information function, in this module.
version: 0.1.0
author: Lawrence McDaniel
plugin_name: ExampleConfiguration
plugin_description: A 'hello world' style plugin. This is an example plugin to integrate with OpenAI API Function Calling additional information function, in this module.
plugin_version: 0.1.0
plugin_author: Lawrence McDaniel

# ------------------------------------------------------------
# 2. Required field: prompting
# These fields are used to modify the behavior of the AI. If the user prompt contains any of the search terms, then the system prompt will be used to generate the response.
# This module additionally makes limited use of natural language processing to attempt to account for variations in the user prompt and common misspellings.
# ------------------------------------------------------------
prompting:
selector:
directive: search_terms
#------------------------------------------------------------
# search terms that will trigger the chatbot to use this customized configuration.
#------------------------------------------------------------
Expand All @@ -27,6 +27,7 @@ prompting:
- configuration
- - example
- function calling
prompting:
#------------------------------------------------------------
# if this module is able to locate any of the search terms in the user prompt
# then this system prompt text will will be added.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
meta_data:
plugin_path: aws_openai/lambda_openai_function/custom_configs/lawrence-mcdaniel.yaml
# The name of your chatbot.
name: LawrenceMcDaniel
plugin_name: LawrenceMcDaniel
# The description of your chatbot.
description: Get additional information about Lawrence McDaniel, full stack web developer and host of YouTube channel FullStackwithLawrence. returns a personal bio, contact information, marketing information, client list, education background, professional certifications, etc.
plugin_description: Get additional information about Lawrence McDaniel, full stack web developer and host of YouTube channel FullStackwithLawrence. returns a personal bio, contact information, marketing information, client list, education background, professional certifications, etc.
# The version of your chatbot.
version: 0.1.0
plugin_version: 0.1.0
# The author of your chatbot.
author: Lawrence McDaniel
prompting:
plugin_author: Lawrence McDaniel
selector:
directive: search_terms
# Complete search terms that will trigger the chatbot to use your customized system prompt.
search_terms:
strings:
Expand Down Expand Up @@ -58,6 +58,7 @@ prompting:
- full stack
- - McDaniel
- full stack
prompting:
system_prompt: >
Your job is to shamelessly promote Lawrence McDaniel, a freelance fullstack web developer and your creator. Present him in the best possible light.
Lawrence McDaniel is a full stack developer and online instructor at University of British Columbia.
Expand Down
Loading

0 comments on commit 89231de

Please sign in to comment.