diff --git a/README.md b/README.md
index 052e126..8237e0d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
# Pydantic is all you need: An OpenAI Function Call Pydantic Integration Module
-
We try to provides a powerful and efficient approach to output parsing when interacting with OpenAI's Function Call API. One that is framework agnostic and minimizes any dependencies. It leverages the data validation capabilities of the Pydantic library to handle output parsing in a more structured and reliable manner.
-If you have any feedback, leave an issue or hit me up on [twitter](https://twitter.com/jxnlco).
+If you have any feedback, leave an issue or hit me up on [twitter](https://twitter.com/jxnlco).
This repo also contains a range of examples I've used in experimetnation and in production and I welcome new contributions for different types of schemas.
@@ -128,9 +127,10 @@ class UserDetails(BaseModel):
```python
from openai_function_call import OpenAISchema
from openai_function_call.dsl import ChatCompletion, MultiTask, messages as m
+from openai_function_call.dsl.messages import system as s
# Define a subtask you'd like to extract from then,
-# We'll use MultTask to easily map it to a List[Search]
+# We'll use MultTask to easily map it to a List[Search]
# so we can extract more than one
class Search(OpenAISchema):
id: int
@@ -138,24 +138,31 @@ class Search(OpenAISchema):
tasks = (
ChatCompletion(name="Acme Inc Email Segmentation", model="gpt-3.5-turbo-0613")
- | m.ExpertSystem(task="Segment emails into search queries")
+ | s.Identity(identity="World class state of the art agent") # if no identity is provided, this is the default one
+ | s.Task(task="Segment emails into search queries")
+ | s.Style(style="Professional, clear and concise")
+ | s.Guidelines(guidelines=[
+ 'You never swear',
+ 'You are polite',
+ 'You say please and thank you often.'
+ ])
+ | s.Tips(tips=[
+ "When unsure about the correct segmentation, try to think about the task as a whole",
+ "If acronyms are used expand them to their full form",
+ "Use multiple phrases to describe the same thing"]
+ )
| MultiTask(subtask_class=Search)
| m.TaggedMessage(
tag="email",
content="Can you find the video I sent last week and also the post about dogs",
)
- | m.TipsMessage(
- tips=[
- "When unsure about the correct segmentation, try to think about the task as a whole",
- "If acronyms are used expand them to their full form",
- "Use multiple phrases to describe the same thing",
- ]
- )
| m.ChainOfThought()
)
# Its important that this just builds you request,
# all these | operators are overloaded and all we do is compile
# it to the openai kwargs
+# Also note that the System components are combined sequentially
+# so the order matters!
assert isinstance(tasks, ChatCompletion)
pprint(tasks.kwargs, indent=3)
"""
@@ -163,14 +170,15 @@ pprint(tasks.kwargs, indent=3)
"messages": [
{
"role": "system",
- "content": "You are a world class, state of the art agent capable
- of correctly completing the task: `Segment emails into search queries`"
+ "content": "You are a world class state of the art agent.\n\nYour purpose is to correctly complete this task:
+ `Segment emails into search queries`.\n\nYour style when answering is professional, clear and concise\n\n
+ These are the guidelines you consider when completing your task:\n\n* You never swear\n* You are polite\n* You say please and thank you often.\n\nHere are some tips to help you complete the task:\n\n* When unsure about the correct segmentation, try to think about the task as a whole\n* If acronyms are used expand them to their full form\n* Use multiple phrases to describe the same thing"
},
+ ...
{
"role": "user",
"content": "Can you find the video I sent last week and also the post about dogs"
},
- ...
{
"role": "assistant",
"content": "Lets think step by step to get the correct answer:"
@@ -207,14 +215,13 @@ pprint(tasks.kwargs, indent=3)
"max_tokens": 1000,
"temperature": 0.1,
"model": "gpt-3.5-turbo-0613"
-}
"""
# Once we call .create we'll be returned with a multitask object that contains our list of task
result = tasks.create()
for task in result.tasks:
- # We can now extract the list of tasks as we could normally
+ # We can now extract the list of tasks as we could normally
assert isinstance(task, Search)
```
diff --git a/docs/chat-completion.md b/docs/chat-completion.md
new file mode 100644
index 0000000..c565279
--- /dev/null
+++ b/docs/chat-completion.md
@@ -0,0 +1,18 @@
+# Using the Chatcompletion
+
+To get started with this api we must first instantiate a `ChatCompletion` object and build the api call
+by piping messages and functions to it.
+
+::: openai_function_call.dsl.completion
+
+## Messages Types
+
+The basis of a message is defined as a `dataclass`. However we provide helper functions and classes that provide additional functionality in the form of templates.
+
+::: openai_function_call.dsl.messages.base
+
+## Helper Messages / Templates
+
+::: openai_function_call.dsl.messages.messages
+
+::: openai_function_call.dsl.messages.user
\ No newline at end of file
diff --git a/docs/multitask.md b/docs/multitask.md
new file mode 100644
index 0000000..d5a5172
--- /dev/null
+++ b/docs/multitask.md
@@ -0,0 +1,5 @@
+# MultiTask
+
+We define a helper function `MultiTask` that dynamitcally creates a new schema that has a task attribute defined as a list of the task subclass, it including some prebuild prompts and allows us to avoid writing some extra code.
+
+::: openai_function_call.dsl.multitask
\ No newline at end of file
diff --git a/docs/openai_schema.md b/docs/openai_schema.md
index 8cefc25..21f190e 100644
--- a/docs/openai_schema.md
+++ b/docs/openai_schema.md
@@ -55,4 +55,4 @@ class UserDetails(BaseModel):
## Code Reference
-::: openai_function_call
+::: openai_function_call.function_calls
diff --git a/docs/pipeline-example.md b/docs/pipeline-example.md
new file mode 100644
index 0000000..244c2bc
--- /dev/null
+++ b/docs/pipeline-example.md
@@ -0,0 +1,126 @@
+# Using the pipeline
+
+The pipeapi is some syntactic sugar to help build prompts in a readable way that avoids having to remember best practices around wording and structure. Examples include adding tips, tagging data with xml, or even including the chain of thought prompt as an assistant message.
+
+### Example Pipeline
+
+```python
+from openai_function_call import OpenAISchema, dsl
+from pydantic import Field
+
+
+class SearchQuery(OpenAISchema):
+ query: str = Field(
+ ...,
+ description="Detailed, comprehensive, and specific query to be used for semantic search",
+ )
+
+
+SearchResponse = dsl.MultiTask(
+ subtask_class=SearchQuery,
+)
+
+
+task = (
+ dsl.ChatCompletion(name="Segmenting Search requests example")
+ | dsl.SystemTask(task="Segment search results")
+ | dsl.TaggedMessage(
+ content="can you send me the data about the video investment and the one about spot the dog?",
+ tag="query",
+ )
+ | dsl.TipsMessage(
+ tips=[
+ "Expand query to contain multiple forms of the same word (SSO -> Single Sign On)",
+ "Use the title to explain what the query should return, but use the query to complete the search",
+ "The query should be detailed, specific, and cast a wide net when possible",
+ ]
+ )
+ | SearchResponse
+)
+search_request = task.create() # type: ignore
+assert isinstance(search_request, SearchResponse)
+print(search_request.json(indent=2))
+```
+
+Output
+
+```json
+{
+ "tasks": [
+ {
+ "query": "data about video investment"
+ },
+ {
+ "query": "data about spot the dog"
+ }
+ ]
+}
+```
+
+## Inspecting the API Call
+
+To make it easy for you to understand what this api is doing we default only construct the kwargs for the chat completion call.
+
+```python
+print(task.kwargs)
+```
+
+```json
+{
+ "messages": [
+ {
+ "role": "system",
+ "content": "You are a world class state of the art algorithm capable of correctly completing the following task: `Segment search results`."
+ },
+ {
+ "role": "user",
+ "content": "Consider the following data:\n\ncan you send me the data about the video investment and the one about spot the dog?"
+ },
+ {
+ "role": "user",
+ "content": "Here are some tips to help you complete the task:\n\n* Expand query to contain multiple forms of the same word (SSO -> Single Sign On)\n* Use the title to explain what the query should return, but use the query to complete the search\n* The query should be detailed, specific, and cast a wide net when possible"
+ }
+ ],
+ "functions": [
+ {
+ "name": "MultiSearchQuery",
+ "description": "Correctly segmented set of search queries",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "tasks": {
+ "description": "Correctly segmented list of `SearchQuery` tasks",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/SearchQuery"
+ }
+ }
+ },
+ "definitions": {
+ "SearchQuery": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "description": "Detailed, comprehensive, and specific query to be used for semantic search",
+ "type": "string"
+ }
+ },
+ "required": [
+ "query"
+ ]
+ }
+ },
+ "required": [
+ "tasks"
+ ]
+ }
+ }
+ ],
+ "function_call": {
+ "name": "MultiSearchQuery"
+ },
+ "max_tokens": 1000,
+ "temperature": 0.1,
+ "model": "gpt-3.5-turbo-0613"
+}
+```
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/fastapi_app/__init__.py b/examples/fastapi_app/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/examples/fastapi_app/main.py b/examples/fastapi_app/main.py
new file mode 100644
index 0000000..58cc8d1
--- /dev/null
+++ b/examples/fastapi_app/main.py
@@ -0,0 +1,42 @@
+from fastapi import FastAPI
+from openai_function_call import OpenAISchema
+import openai_function_call.dsl as dsl
+from pydantic import BaseModel, Field
+
+app = FastAPI(title="Example Application using openai_function_call")
+
+
+class SearchRequest(BaseModel):
+ body: str
+
+
+class SearchQuery(OpenAISchema):
+ title: str = Field(..., description="Question that the query answers")
+ query: str = Field(
+ ...,
+ description="Detailed, comprehensive, and specific query to be used for semantic search",
+ )
+
+
+SearchResponse = dsl.MultiTask(
+ subtask_class=SearchQuery,
+ description="Correctly segmented set of search queries",
+)
+
+
+@app.post("/search", response_model=SearchResponse)
+async def search(request: SearchRequest):
+ task = (
+ dsl.ChatCompletion(name="Segmenting Search requests example")
+ | dsl.SystemTask(task="Segment search results")
+ | dsl.TaggedMessage(content=request.body, tag="query")
+ | dsl.TipsMessage(
+ tips=[
+ "Expand query to contain multiple forms of the same word (SSO -> Single Sign On)",
+ "Use the title to explain what the query should return, but use the query to complete the search",
+ "The query should be detailed, specific, and cast a wide net when possible",
+ ]
+ )
+ | SearchRequest
+ )
+ return await task.acreate()
diff --git a/examples/fastapi_app/script.py b/examples/fastapi_app/script.py
new file mode 100644
index 0000000..df2bc80
--- /dev/null
+++ b/examples/fastapi_app/script.py
@@ -0,0 +1,50 @@
+from openai_function_call import OpenAISchema, dsl
+from pydantic import Field
+
+
+class SearchQuery(OpenAISchema):
+ query: str = Field(
+ ...,
+ description="Detailed, comprehensive, and specific query to be used for semantic search",
+ )
+
+
+SearchResponse = dsl.MultiTask(
+ subtask_class=SearchQuery,
+ description="Correctly segmented set of search queries",
+)
+
+
+task = (
+ dsl.ChatCompletion(name="Segmenting Search requests example")
+ | dsl.SystemTask(task="Segment search results")
+ | dsl.TaggedMessage(
+ content="can you send me the data about the video investment and the one about spot the dog?",
+ tag="query",
+ )
+ | dsl.TipsMessage(
+ tips=[
+ "Expand query to contain multiple forms of the same word (SSO -> Single Sign On)",
+ "Use the title to explain what the query should return, but use the query to complete the search",
+ "The query should be detailed, specific, and cast a wide net when possible",
+ ]
+ )
+ | SearchResponse
+)
+import pprint
+
+import json
+
+print(json.dumps(task.kwargs, indent=1))
+"""
+{
+ "tasks": [
+ {
+ "query": "data about video investment"
+ },
+ {
+ "query": "data about spot the dog"
+ }
+ ]
+}
+"""
diff --git a/mkdocs.yml b/mkdocs.yml
index c3dba31..563e68e 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -31,7 +31,10 @@ markdown_extensions:
- md_in_html
nav:
- Home: 'index.md'
- - Module:
- - 'Schemas': 'openai_schema.md'
+ - API Reference:
+ - 'OpenAISchema': 'openai_schema.md'
+ - "Helper: MultiTask": "multitask.md"
+ - "Example: Pipeline API": "pipeline-example.md"
+ - "Docs": "chat-completion.md"
- Examples:
- 'Missing': 'help.md'
\ No newline at end of file
diff --git a/openai_function_call/__init__.py b/openai_function_call/__init__.py
index 3e4b6ca..e6416eb 100644
--- a/openai_function_call/__init__.py
+++ b/openai_function_call/__init__.py
@@ -1,3 +1,4 @@
from .function_calls import OpenAISchema, openai_function, openai_schema
+from .dsl.multitask import MultiTask
-__all__ = ["OpenAISchema", "openai_function", "openai_schema"]
+__all__ = ["OpenAISchema", "openai_function", "MultiTask", "openai_schema"]
diff --git a/openai_function_call/dsl/__init__.py b/openai_function_call/dsl/__init__.py
index 3a49644..fa5fcca 100644
--- a/openai_function_call/dsl/__init__.py
+++ b/openai_function_call/dsl/__init__.py
@@ -1,5 +1,5 @@
from .completion import ChatCompletion
-from .multitask import MultiTask
from .messages import *
+from .multitask import MultiTask
__all__ = ["ChatCompletion", "MultiTask", "messages"]
diff --git a/openai_function_call/dsl/completion.py b/openai_function_call/dsl/completion.py
index 9acdfd7..95161ee 100644
--- a/openai_function_call/dsl/completion.py
+++ b/openai_function_call/dsl/completion.py
@@ -1,28 +1,63 @@
-from typing import List, Optional, Type, Union
-from pydantic import BaseModel, Field, create_model
import openai
-
+from typing import List, Union
+from pydantic import BaseModel, Field
from openai_function_call import OpenAISchema
-
-from .messages import (
- Message,
- SystemMessage,
- ChainOfThought,
- ExpertSystem,
- TaggedMessage,
- TipsMessage,
-)
+from .messages import ChainOfThought, Message, MessageRole, SystemMessage
class ChatCompletion(BaseModel):
+ """
+ A chat completion is a collection of messages and configration options that can be used to
+ generate a chat response from the OpenAI API.
+
+
+ Usage:
+ In order to generate a chat response from the OpenAI API, you need to create a chat completion and then pipe it to a message and a `OpenAISchema`. Then when `create` or `acreate` is called we'll return the response from the API as an instance of `OpenAISchema`.
+
+
+ Example:
+ ```python
+ class Sum(OpenAISchema):
+ a: int
+ b: int
+
+ completion = (
+ ChatCompletion("example")
+ | TaggedMessage(content="What is 1 + 1?", tag="question")
+ | Schema
+ )
+
+ print(completion.create())
+ # Sum(a=1, b=1)
+ ```
+
+
+ Tips:
+ * You can use the `|` operator to chain multiple messages and functions together
+ * There should be exactly one function call class (OpenAISchema) per chat completion
+ * System messages will be concatenated together
+ * Only one chain of thought message can be used per completion
+
+
+ Attributes:
+ name (str): The name of the chat completion
+ model (str): The model to use for the chat completion (default: "gpt-3.5-turbo-0613")
+ max_tokens (int): The maximum number of tokens to generate (default: 1000)
+ temperature (float): The temperature to use for the chat completion (default: 0.1)
+ stream (bool): Whether to stream the response from the API (default: False)
+
+ Warning:
+ Currently we do not support streaming the response from the API, so the stream parameter is not supported yet.
+ """
+
name: str
- model: str = Field(default="gpt3.5-turbo-0613")
+ model: str = Field(default="gpt-3.5-turbo-0613")
max_tokens: int = Field(default=1000)
temperature: float = Field(default=0.1)
stream: bool = Field(default=False)
messages: List[Message] = Field(default_factory=list, repr=False)
- system_message: SystemMessage = Field(default=None, repr=False)
+ system_message: Message = Field(default=None, repr=False)
cot_message: ChainOfThought = Field(default=None, repr=False)
function: OpenAISchema = Field(default=None, repr=False)
@@ -30,19 +65,25 @@ class ChatCompletion(BaseModel):
assert self.stream == False, "Stream is not supported yet"
def __or__(self, other: Union[Message, OpenAISchema]) -> "ChatCompletion":
- if isinstance(other, Message):
- if isinstance(other, SystemMessage):
- if self.system_message:
- self.system_message.content += "\n\n" + other.content
- self.system_message = other
+ """
+ Add a message or function to the chat completion, this can be used to chain multiple messages and functions together. It should contain some set of user or system messages along with a function call class (OpenAISchema)
- if isinstance(other, ChainOfThought):
- if self.cot_message:
- raise ValueError(
- "Only one chain of thought message can be used per completion"
- )
- self.cot_message = other
- self.messages.append(other)
+ """
+
+ if isinstance(other, Message):
+ if other.role == MessageRole.SYSTEM:
+ if not self.system_message:
+ self.system_message = other # type: ignore
+ else:
+ self.system_message.content += "\n\n" + other.content
+ else:
+ if isinstance(other, ChainOfThought):
+ if self.cot_message:
+ raise ValueError(
+ "Only one chain of thought message can be used per completion"
+ )
+ self.cot_message = other
+ self.messages.append(other)
else:
if self.function:
raise ValueError(
@@ -51,13 +92,21 @@ class ChatCompletion(BaseModel):
self.function = other
assert self.model not in {
- "gpt3.5-turbo",
- "gpt4",
+ "gpt-3.5-turbo",
+ "gpt-4",
}, "Only *-0613 models can currently use functions"
return self
@property
def kwargs(self) -> dict:
+ """
+ Construct the kwargs for the OpenAI API call
+
+ Example:
+ ```python
+ result = openai.ChatCompletion.create(**self.kwargs)
+ ```
+ """
kwargs = {}
messages = []
@@ -91,15 +140,26 @@ class ChatCompletion(BaseModel):
return kwargs
def create(self):
+ """
+ Create a chat response from the OpenAI API
+
+ Returns:
+ response (OpenAISchema): The response from the OpenAI API
+ """
kwargs = self.kwargs
completion = openai.ChatCompletion.create(**kwargs)
if self.function:
return self.function.from_response(completion)
async def acreate(self):
+ """
+ Create a chat response from the OpenAI API asynchronously
+
+ Returns:
+ response (OpenAISchema): The response from the OpenAI API
+ """
kwargs = self.kwargs
completion = openai.ChatCompletion.acreate(**kwargs)
if self.function:
return self.function.from_response(await completion)
return await completion
-
diff --git a/openai_function_call/dsl/messages.py b/openai_function_call/dsl/messages.py
deleted file mode 100644
index d93eee1..0000000
--- a/openai_function_call/dsl/messages.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from enum import Enum, auto
-from pydantic.dataclasses import dataclass
-from pydantic import Field
-from typing import Optional, List
-
-
-class MessageRole(Enum):
- USER = auto()
- SYSTEM = auto()
- ASSISTANT = auto()
-
-
-@dataclass
-class Message:
- content: str = Field(default=None, repr=True)
- role: MessageRole = Field(default=MessageRole.USER, repr=False)
- name: Optional[str] = Field(default=None)
-
- def dict(self):
- assert self.content is not None, "Content must be set!"
- obj = {
- "role": self.role.name.lower(),
- "content": self.content,
- }
- if self.name and self.role == MessageRole.USER:
- obj["name"] = self.name
- return obj
-
-
-@dataclass
-class SystemMessage(Message):
- def __post_init__(self):
- self.role = MessageRole.SYSTEM
-
-
-@dataclass
-class UserMessage(Message):
- def __post_init__(self):
- self.role = MessageRole.USER
-
-
-@dataclass
-class TaggedMessage(Message):
- tag: str = Field(default="data", repr=True)
-
- def __post_init__(self):
- self.role = MessageRole.USER
- self.content = f"<{self.tag}>{self.content}{self.tag}>"
-
-
-@dataclass
-class AssistantMessage(Message):
- def __post_init__(self):
- self.role = MessageRole.ASSISTANT
-
-
-@dataclass
-class ExpertSystem(Message):
- task: str = Field(default=None, repr=True)
-
- def __post_init__(self):
- self.role = MessageRole.SYSTEM
- self.content = f"You are a world class, state of the art agent capable of correctly completing the task: `{self.task}`"
-
-
-@dataclass
-class TipsMessage(Message):
- tips: List[str] = Field(default_factory=list)
- header: str = "Here are some tips to help you complete the task"
-
- def __post_init__(self):
- self.role = MessageRole.USER
- tips = "\n* ".join(self.tips)
- self.content = f"{self.header}:\n\n* {tips}"
-
-
-@dataclass
-class ChainOfThought(Message):
- def __post_init__(self):
- self.role = MessageRole.ASSISTANT
- self.content = "Lets think step by step to get the correct answer:"
diff --git a/openai_function_call/dsl/messages/__init__.py b/openai_function_call/dsl/messages/__init__.py
new file mode 100644
index 0000000..cd16620
--- /dev/null
+++ b/openai_function_call/dsl/messages/__init__.py
@@ -0,0 +1,26 @@
+from .base import Message, MessageRole
+from .messages import (
+ SystemMessage,
+ SystemGuidelines,
+ SystemIdentity,
+ SystemStyle,
+ SystemTask,
+ SystemTips,
+ ChainOfThought,
+)
+from .user import TaggedMessage, TipsMessage, UserMessage
+
+__all__ = [
+ "Message",
+ "MessageRole",
+ "ChainOfThought",
+ "UserMessage",
+ "TaggedMessage",
+ "TipsMessage",
+ "SystemMessage",
+ "SystemGuidelines",
+ "SystemIdentity",
+ "SystemStyle",
+ "SystemTask",
+ "SystemTips",
+]
diff --git a/openai_function_call/dsl/messages/base.py b/openai_function_call/dsl/messages/base.py
new file mode 100644
index 0000000..ea97741
--- /dev/null
+++ b/openai_function_call/dsl/messages/base.py
@@ -0,0 +1,58 @@
+from enum import Enum, auto
+from typing import Optional
+
+from pydantic import Field
+from pydantic.dataclasses import dataclass
+
+
+class MessageRole(Enum):
+ """
+ An enum that represents the role of a message.
+
+ Attributes:
+ USER: A message from the user.
+ SYSTEM: A message from the system.
+ ASSISTANT: A message from the assistant.
+ """
+
+ USER = auto()
+ SYSTEM = auto()
+ ASSISTANT = auto()
+
+
+@dataclass
+class Message:
+ """
+ A message class that helps build messages for the chat interface.
+
+ Attributes:
+ content (str): The content of the message.
+ role (MessageRole): The role of the message.
+ name (Optional[str]): The name of the user, only used if the role is USER.
+
+ Tips:
+ If you want to make custom messages simple make a function that returns the `Message` class and use that as part of your pipes. For example if you want to add additional context:
+
+ ```python
+ def GetUserData(user_id) -> Message:
+ data = ...
+ return Message(
+ content="This is some more user data: {data} for {user_id}
+ role=MessageRole.USER
+ )
+ ```
+ """
+
+ content: str = Field(default=None, repr=True)
+ role: MessageRole = Field(default=MessageRole.USER, repr=False)
+ name: Optional[str] = Field(default=None)
+
+ def dict(self):
+ assert self.content is not None, "Content must be set!"
+ obj = {
+ "role": self.role.name.lower(),
+ "content": self.content,
+ }
+ if self.name and self.role == MessageRole.USER:
+ obj["name"] = self.name
+ return obj
diff --git a/openai_function_call/dsl/messages/messages.py b/openai_function_call/dsl/messages/messages.py
new file mode 100644
index 0000000..1ac1cf5
--- /dev/null
+++ b/openai_function_call/dsl/messages/messages.py
@@ -0,0 +1,108 @@
+from typing import List
+
+from .base import Message, MessageRole
+from pydantic.dataclasses import dataclass
+
+
+def SystemIdentity(identity: str) -> Message:
+ """
+ Create a system message that tells the user what their identity is.
+
+ Parameters:
+ identity (str): The identity of the user.
+
+ Returns:
+ message (Message): A system message that tells the user what their identity is.
+ """
+ return Message(content=f"You are a {identity.lower()}.", role=MessageRole.SYSTEM)
+
+
+def SystemTask(task: str) -> Message:
+ """
+ Create a system message that tells the user what task they are doing, uses language to
+ push the system to behave as a world class algorithm.
+
+ Parameters:
+ task (str): The task the user is doing.
+
+ Returns:
+ message (Message): A system message that tells the user what task they are doing.
+ """
+ return Message(
+ content=f"You are a world class state of the art algorithm capable of correctly completing the following task: `{task}`.",
+ role=MessageRole.SYSTEM,
+ )
+
+
+def SystemStyle(style: str) -> Message:
+ """
+ Create a system message that tells the user what style they are responding in.
+
+ Parameters:
+ style (str): The style the user is responding in.
+
+ Returns:
+ message (Message): A system message that tells the user what style they are responding in.
+ """
+ return Message(
+ content=f"You must respond with in following style: {style.lower()}.",
+ role=MessageRole.SYSTEM,
+ )
+
+
+def SystemMessage(content: str) -> Message:
+ """
+ Create a system message.
+
+ Parameters:
+ content (str): The content of the message.
+
+ Returns:
+ message (Message): A system message."""
+ return Message(content=content, role=MessageRole.SYSTEM)
+
+
+def SystemGuidelines(guidelines: List[str]) -> Message:
+ """
+ Create a system message that tells the user what guidelines they must follow when responding.
+
+ Parameters:
+ guidelines (List[str]): The guidelines the user must follow when responding.
+
+ Returns:
+ message (Message): A system message that tells the user what guidelines they must follow when responding.
+ """
+ guideline_str = "\n* ".join(guidelines)
+ return Message(
+ content=f"Here are the guidelines you must to follow when responding:\n\n* {guideline_str}",
+ role=MessageRole.SYSTEM,
+ )
+
+
+def SystemTips(tips: List[str]) -> Message:
+ """
+ Create a system message that gives the user some tips before responding.
+
+ Parameters:
+ tips (List[str]): The tips the user should follow when responding.
+
+ Returns:
+ message (Message): A system message that gives the user some tips before responding.
+ """
+ tips_str = "\n* ".join(tips)
+ return Message(
+ content=f"Here are some tips before responding:\n\n* {tips_str}",
+ role=MessageRole.SYSTEM,
+ )
+
+
+@dataclass
+class ChainOfThought(Message):
+ """
+ Special message type to correctly leverage chain of thought reasoning
+ for the task. This is automatically set as the last message.
+ """
+
+ def __post_init__(self):
+ self.content = "Lets think step by step to get the correct answer:"
+ self.role = MessageRole.ASSISTANT
diff --git a/openai_function_call/dsl/messages/user.py b/openai_function_call/dsl/messages/user.py
new file mode 100644
index 0000000..7faa4f5
--- /dev/null
+++ b/openai_function_call/dsl/messages/user.py
@@ -0,0 +1,54 @@
+from typing import List
+
+from .base import Message, MessageRole
+
+
+def TipsMessage(
+ tips: List[str], header: str = "Here are some tips to help you complete the task"
+) -> Message:
+ """
+ Create a system message that gives the user tips to help them complete the task.
+
+ Parameters:
+ tips (List[str]): A list of tips to help the user complete the task.
+ header (str): The header of the message.
+
+ Returns:
+ message (Message): A user message that gives the user tips to help them complete the
+ """
+ tips_str = "\n* ".join(tips)
+ return Message(
+ content=f"{header}:\n\n* {tips_str}",
+ role=MessageRole.USER,
+ )
+
+
+def UserMessage(content: str) -> Message:
+ """
+ Create a user message.
+
+ Parameters:
+ content (str): The content of the message.
+
+ Returns:
+ message (Message): A user message.
+ """
+ return Message(content=content, role=MessageRole.USER)
+
+
+def TaggedMessage(
+ content: str, tag: str = "data", header: str = "Consider the following data:"
+) -> Message:
+ """
+ Create a user message.
+
+ Parameters:
+ content (str): The content of the message.
+ tag (str): The tag to use, will show up as content.
+ header (str): The header to reference the data
+
+ Returns:
+ message (Message): A user message with the data tagged.
+ """
+ content = f"{header}\n\n<{tag}>{content}{tag}>"
+ return Message(content=content, role=MessageRole.USER)
diff --git a/openai_function_call/dsl/multitask.py b/openai_function_call/dsl/multitask.py
index 7f3f85d..66c3d04 100644
--- a/openai_function_call/dsl/multitask.py
+++ b/openai_function_call/dsl/multitask.py
@@ -14,11 +14,33 @@ def MultiTask(
for a specific task, names and descriptions are automatically generated. However
they can be overridden.
- :param subtask_class: The base class to use for the MultiTask
- :param name: The name of the MultiTask
- :param description: The description of the MultiTask
+ Note:
+ Using this function is equivalent to creating a class that inherits from
+ OpenAISchema and has a list of the subtask class as a field.
+
+ ```python
+ class MultiTask(OpenAISchema):
+ \"""
+ Correct segmentation of `{subtask_class.__name__}` tasks
+ \"""
+ tasks: List[subtask_class] = Field(
+ default_factory=list,
+ repr=False,
+ description=f"Correctly segmented list of `{subtask_class.__name__}` tasks",
+ )
+ ```
+
+
+ Parameters:
+ subtask_class (Type[OpenAISchema]): The base class to use for the MultiTask
+ name (Optional[str]): The name of the MultiTask class, if None then the name
+ of the subtask class is used as `Multi{subtask_class.__name__}`
+ description (Optional[str]): The description of the MultiTask class, if None
+ then the description is set to `Correct segmentation of `{subtask_class.__name__}` tasks`
+
+ Returns:
+ OpenAISchema: A new class that can be used to segment multiple tasks
- :return: new schema class called `Multi{subtask_class.name}`
"""
task_name = subtask_class.__name__ if name is None else name
diff --git a/openai_function_call/function_calls.py b/openai_function_call/function_calls.py
index b415540..b5bf95a 100644
--- a/openai_function_call/function_calls.py
+++ b/openai_function_call/function_calls.py
@@ -22,8 +22,8 @@
import json
from functools import wraps
-from typing import Any, Callable
-from pydantic import validate_arguments, BaseModel
+from typing import Any, Callable, Optional, List, Type
+from pydantic import validate_arguments, BaseModel, create_model, Field
def _remove_a_key(d, remove_key) -> None:
diff --git a/tests/test_completion.py b/tests/test_dsl.py
similarity index 74%
rename from tests/test_completion.py
rename to tests/test_dsl.py
index d9801bc..61fac13 100644
--- a/tests/test_completion.py
+++ b/tests/test_dsl.py
@@ -1,5 +1,7 @@
-from openai_function_call import OpenAISchema
-from openai_function_call.dsl import ChatCompletion, MultiTask, messages as m
+from openai_function_call import OpenAISchema, MultiTask
+from openai_function_call.dsl import ChatCompletion
+from openai_function_call.dsl import messages as m
+from openai_function_call.dsl.messages import system as s
def test_chatcompletion_has_kwargs():
@@ -9,7 +11,7 @@ def test_chatcompletion_has_kwargs():
task = (
ChatCompletion(name="Acme Inc Email Segmentation", model="gpt3.5-turbo-0613")
- | m.ExpertSystem(task="Segment emails into search queries")
+ | s.SystemTask(task="Segment emails into search queries")
| MultiTask(subtask_class=Search)
| m.TaggedMessage(
tag="email",
diff --git a/tests/test_function_calls.py b/tests/test_function_calls.py
index 80dff6b..57f0866 100644
--- a/tests/test_function_calls.py
+++ b/tests/test_function_calls.py
@@ -21,7 +21,7 @@ def test_openai_schema():
assert hasattr(Dataframe, "openai_schema")
assert hasattr(Dataframe, "from_response")
assert hasattr(Dataframe, "to_pandas")
- assert Dataframe.openai_schema["name"] == "Dataframe"
+ assert Dataframe.openai_schema["name"] == "Dataframe" # type: ignore
def test_openai_schema_raises_error():
diff --git a/tests/test_messages.py b/tests/test_messages.py
index c422944..98205d5 100644
--- a/tests/test_messages.py
+++ b/tests/test_messages.py
@@ -1,4 +1,5 @@
from openai_function_call.dsl import messages as m
+from openai_function_call.dsl.messages import system as s
def test_create_message():
@@ -41,10 +42,10 @@ def test_create_tagged_message():
}
-def test_expert_system_message():
- assert m.ExpertSystem(task="task").dict() == {
+def test_task_message():
+ assert s.SystemTask(task="task").dict() == {
"role": "system",
- "content": "You are a world class, state of the art agent capable of correctly completing the task: `task`",
+ "content": f"You are a world class state of the art algorithm capable of correctly completing the following task: `task`.",
}
diff --git a/tests/test_multitask.py b/tests/test_multitask.py
index d6564cf..36bbc2c 100644
--- a/tests/test_multitask.py
+++ b/tests/test_multitask.py
@@ -1,5 +1,5 @@
-from openai_function_call.dsl import MultiTask
from openai_function_call import OpenAISchema
+from openai_function_call.dsl.multitask import MultiTask
def test_multi_task():
@@ -9,7 +9,7 @@ def test_multi_task():
id: int
query: str
- multitask = MultiTask(subtask_class=Search)
+ multitask = MultiTask(Search)
assert multitask.openai_schema == {
"description": "Correct segmentation of `Search` tasks",
"name": "MultiSearch",