mirror of
https://github.com/kennethreitz/instructor.git
synced 2026-06-05 22:50:18 +00:00
feat(instructor/patch.py): change default mode from FUNCTIONS to TOOLS (#436)
This commit is contained in:
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user