feat(instructor/patch.py): change default mode from FUNCTIONS to TOOLS (#436)

This commit is contained in:
Jason Liu
2024-02-16 11:50:34 -05:00
committed by GitHub
parent e579144171
commit 2143805508
3 changed files with 136 additions and 14 deletions
+2 -5
View File
@@ -6,11 +6,7 @@ Instructor enhances client functionality with three new keywords for backwards c
- `max_retries`: Determines retry attempts for failed `chat.completions.create` validations.
- `validation_context`: Provides extra context to the validation process.
There are three methods for structured output:
1. **Function Calling**: The primary method. Use this for stability and testing.
2. **Tool Calling**: Useful in specific scenarios; lacks the reasking feature of OpenAI's tool calling API.
3. **JSON Mode**: Offers closer adherence to JSON but with more potential validation errors. Suitable for specific non-function calling clients.
The default mode is `instructor.Mode.TOOLS` which is the recommended mode for OpenAI clients. This mode is the most stable and is the most recommended for OpenAI clients. The other modes are for other clients and are not recommended for OpenAI clients.
## Tool Calling
@@ -30,6 +26,7 @@ Parallel tool calling is also an option but you must set `response_model` to be
```python
import instructor
from openai import OpenAI
client = instructor.patch(OpenAI(), mode=instructor.Mode.PARALLEL_TOOLS)
```
+125
View File
@@ -0,0 +1,125 @@
import instructor
from pydantic import BaseModel, Field
from typing import Iterable, List, Optional
from openai import OpenAI
from rich.console import Console
client = instructor.patch(OpenAI())
class ActionItem(BaseModel):
slug: str = Field(..., description="compact short slug")
title: str = Field(description="The title of the action item")
chain_of_thought: str = Field(
description="Short chain of thought that led to this action item, specifically think about whether or not a task should be marked as completed"
)
is_completed: Optional[bool] = Field(
False, description="Whether the action item is completed"
)
class ActionItemResponse(BaseModel):
action_items: Optional[List[ActionItem]] = Field(
..., title="The list of action items"
)
def patch(self, action_item: ActionItem):
current_items = {item.slug: item for item in self.action_items}
current_items[action_item.slug] = action_item
new_response = ActionItemResponse(action_items=list(current_items.values()))
print(f"BEFORE\n{self}\n\nAFTER\n{new_response}")
return new_response
def __repr__(self):
completed_str = "DONE -"
pending_str = "TODO -"
def format_item(item):
return f"{completed_str if item.is_completed else pending_str} {item.title}"
return "\n\n".join([format_item(item) for item in self.action_items])
def __str__(self) -> str:
return self.__repr__()
console = Console()
def yield_action_items(transcript: str, state: ActionItemResponse):
action_items = client.chat.completions.create(
model="gpt-4-turbo-preview",
temperature=0,
seed=42,
response_model=Iterable[ActionItem],
stream=True,
messages=[
{
"role": "system",
"content": f"""
You're a world-class note taker.
You are given the current state of the notes and an additional piece of the transcript.
Use this to update the action.
If you return an action item with the same ID as something in the set, It will be overwritten.
Use this to update the complete status or change the title if there's more context.
- If they are distinct items, do not repeat the slug.
- Only repeat a slug if we need to update the title or completion status.
- If the completion status is not mentioned, it should be assumed to be incomplete.
- For each task describe the success / completion criteria as well.
- If something is explicitly mentioned as being done, mark it as done.
{state.model_dump_json(indent=2)}
""",
},
{
"role": "user",
"content": f"Take the following transcript to return a set of transactions from the transcript\n\n{transcript}",
},
],
)
for action_item in action_items:
state = state.patch(action_item)
yield state
transcript = """
Bob: Great, Carol. I'll handle the back-end optimization then.
Alice: Perfect. Now, after the authentication system is improved, we have to integrate it with our new billing system. That's a medium priority task.
Bob: Sure, but I'll need to complete the back-end optimization of the authentication system first, so it's dependent on that.
Jason: The backend optimization was finished last week actually.
Alice: Understood. Lastly, we also need to update our user documentation to reflect all these changes. It's a low-priority task but still important.
""".strip().split("\n\n")
def text_to_speech(chunk):
"""
Uses a subprocess to convert text to speech via the `say` command on macOS.
"""
import subprocess
subprocess.run(["say", chunk], check=True)
def process_transcript(transcript: List[str]):
state = ActionItemResponse(action_items=[])
for chunk in transcript:
console.print(f"update: {chunk}")
for new_state in yield_action_items(chunk, state):
state = new_state
console.clear()
console.print("# Action Items")
console.print(str(state))
console.print("\n")
if __name__ == "__main__":
process_transcript(transcript)
+9 -9
View File
@@ -174,7 +174,7 @@ def process_response(
stream: bool,
validation_context: dict = None,
strict=None,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> Union[T_Model, T]:
"""Processes a OpenAI response with the response model, if available.
@@ -229,7 +229,7 @@ async def process_response_async(
stream: bool = False,
validation_context: dict = None,
strict: Optional[bool] = None,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> T:
"""Processes a OpenAI response with the response model, if available.
It can use `validation_context` and `strict` to validate the response
@@ -284,7 +284,7 @@ async def retry_async(
kwargs,
max_retries: int | AsyncRetrying = 1,
strict: Optional[bool] = None,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> T:
total_usage = CompletionUsage(completion_tokens=0, prompt_tokens=0, total_tokens=0)
@@ -369,7 +369,7 @@ def retry_sync(
kwargs,
max_retries: int | Retrying = 1,
strict: Optional[bool] = None,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
):
total_usage = CompletionUsage(completion_tokens=0, prompt_tokens=0, total_tokens=0)
@@ -486,7 +486,7 @@ class InstructorChatCompletionCreate(Protocol):
@overload
def patch(
client: OpenAI,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> OpenAI:
...
@@ -494,7 +494,7 @@ def patch(
@overload
def patch(
client: AsyncOpenAI,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> AsyncOpenAI:
...
@@ -502,7 +502,7 @@ def patch(
@overload
def patch(
create: Callable[T_ParamSpec, T_Retval],
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> InstructorChatCompletionCreate:
...
@@ -510,7 +510,7 @@ def patch(
def patch(
client: Union[OpenAI, AsyncOpenAI] = None,
create: Callable[T_ParamSpec, T_Retval] = None,
mode: Mode = Mode.FUNCTIONS,
mode: Mode = Mode.TOOLS,
) -> Union[OpenAI, AsyncOpenAI]:
"""
Patch the `client.chat.completions.create` method
@@ -588,7 +588,7 @@ def patch(
return new_create
def apatch(client: AsyncOpenAI, mode: Mode = Mode.FUNCTIONS):
def apatch(client: AsyncOpenAI, mode: Mode = Mode.TOOLS):
"""
No longer necessary, use `patch` instead.