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

[python] Use OutputDataWithValue type for function calls #611

Merged
merged 1 commit into from
Dec 28, 2023
Merged

Conversation

rossdanlm
Copy link
Contributor

@rossdanlm rossdanlm commented Dec 25, 2023

[python] Use OutputDataWithValue type for function calls

This is extending #605 where I converted from model-specific response types --> pure strings. Now I'm going to also support function calls --> OutputData types

Same as last diff (#610) except now for python files instead of typescript

Don't need to do this for the image outputs since I already did that in #608

Actually like typescript, the only model parser we need to support for function calling is openai.py

Copy link
Contributor

@saqadri saqadri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we're better off keeping strings just as strings. Thoughts?

@rossdanlm rossdanlm marked this pull request as draft December 27, 2023 15:07
@rossdanlm rossdanlm changed the title [python] Save output.data with OutputData type instead of pure string [python] Use OutputDataWithValue type for function calls Dec 27, 2023
@rossdanlm rossdanlm force-pushed the pr611 branch 6 times, most recently from bf687d2 to 8d3e926 Compare December 27, 2023 22:42
@rossdanlm rossdanlm marked this pull request as ready for review December 27, 2023 22:47
Copy link
Contributor

@saqadri saqadri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few blocking comments, but otherwise looks good. Really nice work! Not an easy change

# Gemini does not support function calls so shouldn't
# get here, but just being safe
return json.dumps(output_data.value, indent=2)
raise ValueError("Not Implemented")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove? Or update string with a more descriptive error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should never actually get here but yea I can remove

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: Ok I can't remove, I added more detailed error message so it's clear why

accumulated_message += new_text
options.stream_callback(new_text, accumulated_message, 0)
output.data = accumulated_message
if isinstance(new_text, str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the case where new_text isn't str? Can that happen?

Copy link
Contributor Author

@rossdanlm rossdanlm Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: It's actually possible (though practically unlikely), see comments in #611 (comment). I get what you're saying (this change seems unnecessary and more confusing) so I'll remove this type of logic from this diff

No it can't happen for hugging face text generation model parser

# HuggingFace Text generation does not support function
# calls so shouldn't get here, but just being safe
return json.dumps(output_data.value, indent=2)
raise ValueError("Not Implemented")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: update string comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove the value error, but why remove the comment?

Comment on lines 532 to 534
role = output.metadata.get("assistant", None) or \
("rawResponse" in output.metadata and
output.metadata["rawResponse"].get("assistant", None))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. Can you check if TS or Python implementations have this issue elsewhere in your stack?

Suggested change
role = output.metadata.get("assistant", None) or \
("rawResponse" in output.metadata and
output.metadata["rawResponse"].get("assistant", None))
role = output.metadata.get("role", None) or \
("rawResponse" in output.metadata and
output.metadata["rawResponse"].get("role", None))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also perhaps we should default role to "assistant"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, just copy pasted wrong my bad, should be role.

Can you check if TS or Python implementations have this issue elsewhere in your stack?

I don't have this issue anywhere else (https://github.com/lastmile-ai/aiconfig/pull/610/files#diff-e044912cdb054134cac74868d76969daa92c6dd7c1238805672e023f9b7aa92d), this was just a typo

Also perhaps we should default role to "assistant"

We shouldn't be doing this. Role can be one of 5 different types, not just "assistant", so we need to check for that explicitly: https://github.com/openai/openai-node/blob/b595cd953a704dba2aef4c6c3fa431f83f18ccf9/src/resources/chat/completions.ts#L514

Comment on lines +542 to +546
elif isinstance(output_data, OutputDataWithValue):
if isinstance(output_data.value, str):
content = output_data.value
elif output_data.kind == "tool_calls":
assert isinstance(output, OutputDataWithToolCallsValue)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this can be simplified:

if isinstance(output_data, str):
  # Do string stuff
elif isinstance(output_data, OutputDataWithToolCallsValue):
  # Do function call stuff
elif isinstance(output_data, OutputDataWithVale):
  # Do other value stuff where data.value is string
elif isinstance(output_data, ChatCompletionMessage):
  # Do chat completion stuff

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this more complicated and unintuitive. OutputDataWithToolCallsValue is a subset of OutputDataWithValue. I get what you're saying that you can have single else-if instead of nested if-statements, but it's jarring to go from "small subset" --> "bigger subset" while reading down through if-else logic.

This is also more confusing because the inner # Do string stuff, # Do function call stuff etc all need to individually check for the role type being assistant before we can do anything

if message.get("content", None) is not None:
output_data = message.get("content")
elif message.get("tool_calls", None) is not None:
tool_calls = [ToolCallData(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is fine for now, but we might need to update this in the future as tool_calls include things that aren't just function calls

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good callout, I'll do another check where I'll check for the type being 'function'

accumulated_message += new_text
options.stream_callback(new_text, accumulated_message, 0)
output.data = accumulated_message
if isinstance(new_text, str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be anything other than string? These changes seem unnecessary if I'm understanding the streamer correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Streamer can actually contain anything, doesn't have to be text. Theoretically it always be str (except for the stop signal), but I'm just being safe. This is a fork of the HF source code (couldn't find it publicly on GH, but it's this: https://github.com/PaddlePaddle/PaddleNLP/blob/a55039cc16bbcfa06b204a5f21b813e98a3f65ca/paddlenlp/generation/streamers.py#L197-L206)

# HuggingFace Text generation does not support function
# calls so shouldn't get here, but just being safe
return json.dumps(output_data.value, indent=2)
raise ValueError("Not Implemented")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these cases we could technically json.dumps(output_data), right?

Copy link
Contributor Author

@rossdanlm rossdanlm Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should never be possible to get to this point, which is why I raised an error to be cautious. But yea I can just remove error and fall into the final return "" instead

Comment on lines +85 to +86
# If response_includes_details is false, `iteration` will be a string,
# otherwise, `iteration` is a TextGenerationStreamResponse
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update response type to Iterable[Union[TextGenerationStreamResponse, str]] to reflect this?

Copy link
Contributor Author

@rossdanlm rossdanlm Dec 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, this is what HuggingFace does too actually (the iterables are for streaming):
Screenshot 2023-12-28 at 11 22 01

I'll be bit more explicit and use Union[Iterable[str], Iterable[TextGenerationStreamResponse]]

@rholinshead
Copy link
Contributor

For testing, can you please be sure to run the tests and the relevant cookbooks/demo scripts (e.g. to test function calling)

@rossdanlm
Copy link
Contributor Author

rossdanlm commented Dec 28, 2023

Update: Created issue to track this in #659

For testing, can you please be sure to run the tests and the relevant cookbooks/demo scripts (e.g. to test function calling)

I ran the automated tests and function calls still work (notably the test_openai_util.py file). I also went through the python function call cookbook just to be safe and it still works, but I'm not going to do the other 10-ish that we have
Screenshot 2023-12-28 at 11 34 16

I get what you're saying just to test the relevant cookbook, so this below isn't to disagree with your comment, just sharing general thoughts to flag:

I would be surprised if all the cookbooks still worked after these output refactoring PRs from last few days, since we're changing the way we output data and thus changing how we should access it. I feel having automated testing for the cookbooks should be P0 after we finish local editor MVP. I've mentioned to Sarmad that it's simply not feasible to test them all manually. If any of them are broken now, to me having automated testing + fixing them is a separate work-project

This is extending #605 where I converted from model-specific response types --> pure strings. Now I'm going to also support function calls --> OutputData types

Same as last diff (#610) except now for python files instead of typescript

Don't need to do this for the image outputs since I already did that in #608

Actually like typescript, the only model parser we need to support for function calling is `openai.py`
@rossdanlm rossdanlm merged commit 7329eef into main Dec 28, 2023
2 checks passed
@rossdanlm rossdanlm deleted the pr611 branch December 28, 2023 17:47
rossdanlm pushed a commit that referenced this pull request Dec 28, 2023
See comment here for context: #611 (comment)

Separating this diff from #611 to make it easier and get that one unblocked
rossdanlm pushed a commit that referenced this pull request Dec 28, 2023
See comment here for context: #611 (comment)

Separating this diff from #611 to make it easier and get that one unblocked
rossdanlm added a commit that referenced this pull request Dec 28, 2023
Explicit str type checking in model parsers



See comment here for context:
#611 (comment)

Separating this diff from
#611 to make it easier and
get that one unblocked
Victor-Su-Ortiz pushed a commit to Victor-Su-Ortiz/aiconfig that referenced this pull request Jan 4, 2024
See comment here for context: lastmile-ai#611 (comment)

Separating this diff from lastmile-ai#611 to make it easier and get that one unblocked
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

Successfully merging this pull request may close these issues.

3 participants