Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to do both text output and FunctionCall in the same request? #232

Open
jaromiru opened this issue Jun 6, 2024 · 5 comments
Open

How to do both text output and FunctionCall in the same request? #232

jaromiru opened this issue Jun 6, 2024 · 5 comments

Comments

@jaromiru
Copy link

jaromiru commented Jun 6, 2024

Hello, I want the model to reason before making a function call in one request, which is possible in other libraries. However, I haven't found a way to do this in magentic. The usual signature of a method is:

def do_something(param: str) -> FunctionCall: ...

which does not allow any other outputs than the function call.

I'd be happy to do something like:

def do_something(param: str) -> tuple[Annotated[str, "reasoning"], FunctionCall]: ...

but this results in a pydantic error. It applies to ParallelFunctionCall also.

@jackmpcollins
Copy link
Owner

Hi @jaromiru

The OpenAI API returns either a message or tool call(s), while the Anthropic API can return "thoughts" before making a tool call (see related issue #220). Because of this, a return type that allows "thinking" before making a function call would only work with some LLM providers, but it might still be useful to have.

One approach you could try is having separate steps for the thinking and the function call. Something like

@prompt(...)
def think_about_doing_something(param: str) -> str: ...

@prompt("Based on thoughts {thoughts}. Do thing")
def do_something(thoughts: str, param: str) -> str: ...

thoughts = think_about_doing_something(param)
function_call = do_something(thoughts, param)

Another option which would reduce this to a single query would be to set the return type to a pydantic model with an "explanation" field and fields for the function parameters. You would need to do this for all available functions and union them in the return type. Similar to https://magentic.dev/structured-outputs/#chain-of-thought-prompting

class ExplainedFunctionParams(BaseModel):
    explanation: str = Field(description="reasoning for the following choice of parameters")
    param: str

@prompt(...)  # No `functions` provided here
def do_something(param: str) -> ExplainedFunctionParams: ...

func_params = do_something(param)
my_function(func_params.param)

Please let me know if either of these approaches would work for you. Thanks for using magentic.

@jaromiru
Copy link
Author

Hi @jackmpcollins, thanks for the answer and the proposed workarounds. Obviously, both of them have their drawbacks.
My main motivation was to reduce costs by having one LLM call (especially when the context is large), and the first method does not help with this.
The second proposal makes it complicated, brittle, hand-engineered, prone to errors and avoids the ease-of-use of Magentic library.

I currently have no solutions on my own, so we can close the ticket, or keep it open for followup discussion.

@jackmpcollins
Copy link
Owner

jackmpcollins commented Jun 27, 2024

@jaromiru Let's leave this open because it would be great for magentic to support this for the Anthropic API. Please let me know if/how you solve this for OpenAI using another library or their API directly because that could inform how to solve it generally for magentic.

@mnicstruwig
Copy link
Contributor

@jackmpcollins Maybe it's finally a good time to revive this given the new release of Sonnet 3.5!

It doesn't look like Anthropic will be changing this pattern anytime soon.

@jackmpcollins
Copy link
Owner

GPT-4o also supports thinking before tool use. I'll try to get this added soon. Probably will be a new return type that is an iterable of StreamedStr and FunctionCall, similar to how ParallelFunctionCall is an iterable of FunctionCall.

from openai import Client

client = Client()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Say hello, then call the tool for Boston"}],
    stream=True,
    stream_options={"include_usage": True},
    tools=[
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["location"],
                },
            },
        },
    ],
)
for chunk in response:
    print(chunk)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content='', function_call=None, refusal=None, role='assistant', tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content='Hello', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' there', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content='!', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' Let', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' me', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' get', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' the', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' current', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' weather', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' for', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' Boston', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' for', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=' you', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content='.', function_call=None, refusal=None, role=None, tool_calls=None), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id='call_rJqb5hD1Yhot4BFzRm8AbIis', function=ChoiceDeltaToolCallFunction(arguments='', name='get_current_weather'), type='function')]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='{"', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='location', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='":"', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='Boston', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments=',', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments=' MA', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=[ChoiceDeltaToolCall(index=0, id=None, function=ChoiceDeltaToolCallFunction(arguments='"}', name=None), type=None)]), finish_reason=None, index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[Choice(delta=ChoiceDelta(content=None, function_call=None, refusal=None, role=None, tool_calls=None), finish_reason='tool_calls', index=0, logprobs=None)], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=None)
ChatCompletionChunk(id='chatcmpl-AN7qZSMv8i5J9Mrm6IgFgM0lrl9wh', choices=[], created=1730075071, model='gpt-4o-2024-08-06', object='chat.completion.chunk', service_tier=None, system_fingerprint='fp_72bbfa6014', usage=CompletionUsage(completion_tokens=32, prompt_tokens=81, total_tokens=113, completion_tokens_details=CompletionTokensDetails(audio_tokens=None, reasoning_tokens=0), prompt_tokens_details=PromptTokensDetails(audio_tokens=None, cached_tokens=0)))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants