Skip to content

Commit 89231de

Browse files
authored
Merge pull request #312 from FullStackWithLawrence/next
add selector key
2 parents 3a85da5 + 65adb8b commit 89231de

12 files changed

Lines changed: 154 additions & 76 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.coverage
22
.coverage.*
3+
htmlcov
34

45
# Jupyter Notebook
56
.ipynb_checkpoints

api/terraform/python/openai_api/lambda_openai_function/lambda_handler.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,16 @@ def handler(event, context):
8080
for plugin in plugins:
8181
if search_terms_are_in_messages(
8282
messages=messages,
83-
search_terms=plugin.prompting.search_terms.strings,
84-
search_pairs=plugin.prompting.search_terms.pairs,
83+
search_terms=plugin.selector.search_terms.strings,
84+
search_pairs=plugin.selector.search_terms.pairs,
8585
):
8686
model = "gpt-3.5-turbo-1106"
8787
messages = customized_prompt(plugin=plugin, messages=messages)
8888
custom_tool = plugin_tool_factory(plugin=plugin)
8989
tools.append(custom_tool)
90-
print(f"Adding plugin: {plugin.name} {plugin.meta_data.version} created by {plugin.meta_data.author}")
90+
print(
91+
f"Adding plugin: {plugin.name} {plugin.meta_data.plugin_version} created by {plugin.meta_data.plugin_author}"
92+
)
9193

9294
# https://platform.openai.com/docs/guides/gpt/chat-completions-api
9395
validate_item(

api/terraform/python/openai_api/lambda_openai_function/plugin_loader.py

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

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

2123

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

3638

3739
class PluginBase(BaseModel):
@@ -74,7 +76,7 @@ def to_json(self) -> json:
7476
class SearchTerms(PluginBase):
7577
"""Search terms of a Plugin object"""
7678

77-
plugin_json: dict = Field(..., description="Config object")
79+
plugin_json: dict = Field(..., description="Plugin object")
7880

7981
@field_validator("plugin_json")
8082
@classmethod
@@ -120,7 +122,7 @@ def to_json(self) -> json:
120122
class AdditionalInformation(PluginBase):
121123
"""Additional information of a Plugin object"""
122124

123-
plugin_json: dict = Field(..., description="Config object")
125+
plugin_json: dict = Field(..., description="Plugin object")
124126

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

146-
plugin_json: dict = Field(..., description="Config object")
147-
search_terms: SearchTerms = Field(None, description="Search terms of the plugin object")
148+
plugin_json: dict = Field(..., description="Plugin object")
148149
system_prompt: SystemPrompt = Field(None, description="System prompt of the plugin object")
149150

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

161161
@field_validator("plugin_json")
162162
@classmethod
163163
def validate_plugin_json(cls, plugin_json) -> dict:
164164
"""Validate the plugin object"""
165-
required_keys = ["search_terms", "system_prompt"]
165+
required_keys = ["system_prompt"]
166166
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
167+
return plugin_json
168+
169+
@property
170+
def system_prompt(self) -> str:
171+
"""Return the system prompt"""
172+
return self.plugin_json.get("system_prompt")
167173

168174
def to_json(self) -> json:
169175
"""Return the plugin as a JSON object"""
170-
return {
171-
"search_terms": self.search_terms.to_json(),
172-
"system_prompt": self.system_prompt.system_prompt,
173-
}
176+
return self.plugin_json
174177

175178

176179
class FunctionCalling(PluginBase):
177180
"""FunctionCalling child class of a Plugin"""
178181

179-
plugin_json: dict = Field(..., description="Config object")
182+
plugin_json: dict = Field(..., description="Plugin object")
180183
function_description: str = Field(None, description="Description of the function")
181184
additional_information: AdditionalInformation = Field(None, description="Additional information of the function")
182185

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

225-
plugin_json: dict = Field(..., description="Config object")
228+
plugin_json: dict = Field(..., description="Plugin object")
226229

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

242-
required_keys = ["plugin_path", "name", "description", "version", "author"]
245+
required_keys = ["plugin_name", "plugin_description", "plugin_version", "plugin_author"]
243246
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
247+
if str(plugin_json["plugin_version"]) not in VALID_PLUGIN_VERSIONS:
248+
do_error(
249+
class_name=cls.__name__,
250+
err=f"Invalid plugin object: {plugin_json}. 'plugin_version' should be one of {VALID_PLUGIN_VERSIONS}.",
251+
)
244252
return plugin_json
245253

246254
@property
247-
def name(self) -> str:
255+
def plugin_name(self) -> str:
248256
"""Return the name of the plugin object"""
249-
return self.plugin_json.get("name") if self.plugin_json else None
257+
return self.plugin_json.get("plugin_name") if self.plugin_json else None
250258

251259
@property
252-
def plugin_path(self) -> str:
253-
"""Return the path of the plugin object"""
254-
return self.plugin_json.get("plugin_path") if self.plugin_json else None
255-
256-
@property
257-
def description(self) -> str:
260+
def plugin_description(self) -> str:
258261
"""Return the description of the plugin object"""
259-
return self.plugin_json.get("description") if self.plugin_json else None
262+
return self.plugin_json.get("plugin_description") if self.plugin_json else None
260263

261264
@property
262-
def version(self) -> str:
265+
def plugin_version(self) -> str:
263266
"""Return the version of the plugin object"""
264-
return self.plugin_json.get("version") if self.plugin_json else None
267+
return self.plugin_json.get("plugin_version") if self.plugin_json else None
265268

266269
@property
267-
def author(self) -> str:
270+
def plugin_author(self) -> str:
268271
"""Return the author of the plugin object"""
269-
return self.plugin_json.get("author") if self.plugin_json else None
272+
return self.plugin_json.get("plugin_author") if self.plugin_json else None
270273

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

275278

279+
class Selector(PluginBase):
280+
"""Selector of a Plugin object"""
281+
282+
plugin_json: dict = Field(..., description="Plugin object")
283+
directive: str = Field(None, description="Directive of the Selector object")
284+
search_terms: SearchTerms = Field(None, description="Search terms of the Selector object")
285+
286+
@root_validator(pre=True)
287+
def set_fields(cls, values):
288+
"""proxy for __init__() - Set the fields"""
289+
plugin_json = values.get("plugin_json")
290+
291+
if not isinstance(plugin_json, dict):
292+
raise ValueError(f"Expected plugin_json to be a dict but received {type(plugin_json)}")
293+
if plugin_json:
294+
values["directive"] = plugin_json["directive"]
295+
values["search_terms"] = SearchTerms(plugin_json=plugin_json["search_terms"])
296+
return values
297+
298+
@field_validator("plugin_json")
299+
@classmethod
300+
def validate_plugin_json(cls, plugin_json) -> dict:
301+
"""Validate the plugin object"""
302+
required_keys = ["directive", "search_terms"]
303+
validate_required_keys(class_name=cls.__name__, required_keys=required_keys, plugin_json=plugin_json)
304+
if not isinstance(plugin_json["directive"], str):
305+
do_error(
306+
class_name=cls.__name__,
307+
err=f"Invalid plugin object: {plugin_json}. 'directive' should be a string.",
308+
)
309+
310+
@field_validator("directive")
311+
@classmethod
312+
def validate_directive(cls, directive) -> dict:
313+
"""Validate the plugin object"""
314+
if directive not in VALID_DIRECTIVES:
315+
do_error(
316+
class_name=cls.__name__,
317+
err=f"Invalid plugin object: {directive}. 'directive' should be one of {VALID_DIRECTIVES}.",
318+
)
319+
return directive
320+
321+
def to_json(self) -> json:
322+
"""Return the plugin as a JSON object"""
323+
return {
324+
"directive": self.directive,
325+
"search_terms": self.search_terms.to_json(),
326+
}
327+
328+
276329
class Plugin(PluginBase):
277330
"""A json object that contains the plugin for a plugin.function_calling_plugin() function"""
278331

279332
index: int = Field(0, description="Index of the plugin object")
280-
plugin_json: dict = Field(..., description="Config object")
333+
plugin_json: dict = Field(..., description="Plugin object")
334+
335+
# Child classes
281336
meta_data: Optional[MetaData] = Field(None, description="Metadata of the plugin object")
337+
selector: Optional[Selector] = Field(None, description="Selector of the plugin object")
282338
prompting: Optional[Prompting] = Field(None, description="Prompting of the plugin object")
283339
function_calling: Optional[FunctionCalling] = Field(None, description="FunctionCalling of the plugin object")
284340

285341
@property
286342
def name(self) -> str:
287343
"""Return a name in the format: "WillyWonka"""
288-
return self.meta_data.name
344+
return self.meta_data.plugin_name
289345

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

307-
required_keys = ["meta_data", "prompting", "function_calling"]
364+
required_keys = ["meta_data", "selector", "prompting", "function_calling"]
308365
for key in required_keys:
309366
if key not in plugin_json:
310367
cls.do_error(f"Invalid plugin object: {plugin_json}. Missing key: {key}.")
@@ -319,6 +376,7 @@ def to_json(self) -> json:
319376
return {
320377
"name": self.name,
321378
"meta_data": self.meta_data.to_json(),
379+
"selector": self.selector.to_json(),
322380
"prompting": self.prompting.to_json(),
323381
"function_calling": self.function_calling.to_json(),
324382
}
@@ -363,7 +421,7 @@ def __init__(self, plugin_path: str = None, aws_s3_bucket_name: str = None):
363421
plugin = Plugin(plugin_json=plugin_json, index=i)
364422
self._custom_plugins.append(plugin)
365423
print(
366-
f"Loaded plugin from AWS S3 bucket: {plugin.name} {plugin.meta_data.version} created by {plugin.meta_data.author}"
424+
f"Loaded plugin from AWS S3 bucket: {plugin.name} {plugin.meta_data.plugin_version} created by {plugin.meta_data.plugin_author}"
367425
)
368426

369427
@property

api/terraform/python/openai_api/lambda_openai_function/plugins/everlasting-gobstopper.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
meta_data:
3-
plugin_path: aws_openai/lambda_openai_function/custom_configs/everlasting-gobstopper.yaml
43
# The name of your chatbot.
5-
name: EverlastingGobstopper
4+
plugin_name: EverlastingGobstopper
65
# The description of your chatbot.
7-
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.
6+
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.
87
# The version of your chatbot.
9-
version: 0.1.0
8+
plugin_version: 0.1.0
109
# The author of your chatbot.
11-
author: Lawrence McDaniel
12-
prompting:
10+
plugin_author: Lawrence McDaniel
11+
selector:
12+
directive: search_terms
1313
# Complete search terms that will trigger the chatbot to use your customized system prompt.
1414
search_terms:
1515
strings:
@@ -22,6 +22,7 @@ prompting:
2222
- gobstopper
2323
- - everlasting
2424
- gobstoppers
25+
prompting:
2526
system_prompt: >
2627
You are a helpful marketing agent for the [Willy Wonka Chocolate Factory](https://wwcf.com).
2728
function_calling:

api/terraform/python/openai_api/lambda_openai_function/plugins/example-configuration.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
# Each of these fields is required.
55
# ------------------------------------------------------------
66
meta_data:
7-
plugin_path: aws_openai/lambda_openai_function/custom_configs/example-configuration.yaml
8-
name: ExampleConfiguration
9-
description: A 'hello world' style plugin. This is an example plugin to integrate with OpenAI API Function Calling additional information function, in this module.
10-
version: 0.1.0
11-
author: Lawrence McDaniel
7+
plugin_name: ExampleConfiguration
8+
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.
9+
plugin_version: 0.1.0
10+
plugin_author: Lawrence McDaniel
1211

1312
# ------------------------------------------------------------
1413
# 2. Required field: prompting
1514
# 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.
1615
# This module additionally makes limited use of natural language processing to attempt to account for variations in the user prompt and common misspellings.
1716
# ------------------------------------------------------------
18-
prompting:
17+
selector:
18+
directive: search_terms
1919
#------------------------------------------------------------
2020
# search terms that will trigger the chatbot to use this customized configuration.
2121
#------------------------------------------------------------
@@ -27,6 +27,7 @@ prompting:
2727
- configuration
2828
- - example
2929
- function calling
30+
prompting:
3031
#------------------------------------------------------------
3132
# if this module is able to locate any of the search terms in the user prompt
3233
# then this system prompt text will will be added.

api/terraform/python/openai_api/lambda_openai_function/plugins/lawrence-mcdaniel.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
meta_data:
3-
plugin_path: aws_openai/lambda_openai_function/custom_configs/lawrence-mcdaniel.yaml
43
# The name of your chatbot.
5-
name: LawrenceMcDaniel
4+
plugin_name: LawrenceMcDaniel
65
# The description of your chatbot.
7-
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.
6+
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.
87
# The version of your chatbot.
9-
version: 0.1.0
8+
plugin_version: 0.1.0
109
# The author of your chatbot.
11-
author: Lawrence McDaniel
12-
prompting:
10+
plugin_author: Lawrence McDaniel
11+
selector:
12+
directive: search_terms
1313
# Complete search terms that will trigger the chatbot to use your customized system prompt.
1414
search_terms:
1515
strings:
@@ -58,6 +58,7 @@ prompting:
5858
- full stack
5959
- - McDaniel
6060
- full stack
61+
prompting:
6162
system_prompt: >
6263
Your job is to shamelessly promote Lawrence McDaniel, a freelance fullstack web developer and your creator. Present him in the best possible light.
6364
Lawrence McDaniel is a full stack developer and online instructor at University of British Columbia.

0 commit comments

Comments
 (0)