mirror of
https://github.com/kennethreitz/instructor.git
synced 2026-06-05 22:50:18 +00:00
Implement DSL and documentation
This commit is contained in:
@@ -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": "<email>Can you find the video I sent last week and also the post about dogs</email>"
|
||||
},
|
||||
...
|
||||
{
|
||||
"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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -55,4 +55,4 @@ class UserDetails(BaseModel):
|
||||
|
||||
## Code Reference
|
||||
|
||||
::: openai_function_call
|
||||
::: openai_function_call.function_calls
|
||||
|
||||
@@ -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\n<query>can you send me the data about the video investment and the one about spot the dog?</query>"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
```
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
+5
-2
@@ -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'
|
||||
@@ -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"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from .completion import ChatCompletion
|
||||
from .multitask import MultiTask
|
||||
from .messages import *
|
||||
from .multitask import MultiTask
|
||||
|
||||
__all__ = ["ChatCompletion", "MultiTask", "messages"]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:"
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 <tag>content</tag>.
|
||||
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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
@@ -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():
|
||||
|
||||
@@ -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`.",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user