marvin icon indicating copy to clipboard operation
marvin copied to clipboard

Using OpenAI tools with Assistant fails

Open Fakamoto opened this issue 1 year ago • 2 comments

First check

  • [X] I added a descriptive title to this issue.
  • [X] I used the GitHub search to try to find a similar issue and didn't find one.
  • [X] I searched the Marvin documentation for this issue.

Bug summary

Using marvin.beta.assistants.Retrieval marvin.beta.assistants.CodeInterpreter

in Assistant tools fails

Reproduction

from marvin.beta import Assistant
from marvin.beta.assistants import pprint_messages, CodeInterpreter

ai = Assistant(name='Marvin', tools=[CodeInterpreter])
response = ai.say("Generate a plot of sin(x)")

# pretty-print the response
pprint_messages(response)

Error

Traceback (most recent call last):
  File "/Users/test/main.py", line 5, in <module>
    response = ai.say("Generate a plot of sin(x)")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/marvin/utilities/asyncio.py", line 190, in sync_wrapper
    return run_sync(coro)
           ^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/marvin/utilities/asyncio.py", line 103, in run_sync
    return asyncio.run(coroutine)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/[email protected]/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/marvin/beta/assistants/assistants.py", line 96, in say_async
    async with self:
  File "/opt/homebrew/lib/python3.11/site-packages/marvin/beta/assistants/assistants.py", line 117, in __aenter__
    await self.create_async(_auto_delete=True)
  File "/opt/homebrew/lib/python3.11/site-packages/marvin/beta/assistants/assistants.py", line 139, in create_async
    response = await client.beta.assistants.create(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/openai/resources/beta/assistants/assistants.py", line 411, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/openai/_base_client.py", line 1725, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/openai/_base_client.py", line 1428, in request
    return await self._request(
           ^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/lib/python3.11/site-packages/openai/_base_client.py", line 1519, in _request
    raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {'error': {'message': "5 validation errors for Request\nbody -> tools -> 0 -> function\n  extra fields not permitted (type=value_error.extra)\nbody -> tools -> 0 -> type\n  unexpected value; permitted: <ToolTypeParam.RETRIEVAL: 'retrieval'> (type=value_error.const; given=code_interpreter; permitted=(<ToolTypeParam.RETRIEVAL: 'retrieval'>,))\nbody -> tools -> 0 -> function\n  extra fields not permitted (type=value_error.extra)\nbody -> tools -> 0 -> type\n  unexpected value; permitted: <ToolTypeParam.FUNCTION: 'function'> (type=value_error.const; given=code_interpreter; permitted=(<ToolTypeParam.FUNCTION: 'function'>,))\nbody -> tools -> 0 -> function\n  none is not an allowed value (type=type_error.none.not_allowed)", 'type': 'invalid_request_error', 'param': None, 'code': None}}

Versions

marvin version
Version:                2.1.5
Python version:         3.11.7
OS/Arch:                darwin/arm64

openai --version
openai 1.12.0

>>> import pydantic
>>> pydantic.__version__
'2.6.1'

Additional context

No response

Fakamoto avatar Feb 16 '24 18:02 Fakamoto

the problem seems to be that

class RetrievalTool(Tool[T]):
    type: Literal["retrieval"] = "retrieval"

and

class CodeInterpreterTool(Tool[T]):
    type: Literal["code_interpreter"] = "code_interpreter"

inherit from Tool

class Tool(MarvinType, Generic[T]):
    type: str
    function: Optional[Function[T]] = None

which has a function field that is invalid in the Retrieval and CodeInterpreter case, as OpenAI is expecting {"type": "retrieval"}

so when you do

    @expose_sync_method("create")
    async def create_async(self, _auto_delete: bool = False):
        [....]

        response = await client.beta.assistants.create(
            **self.model_dump(
                include={"name", "model", "metadata", "file_ids", "metadata"}
            ),
            tools=[tool.model_dump() for tool in self.get_tools()],
            instructions=self.get_instructions(),
        )

the tool.model_dump() returns {"type": "retrieval", "function": None}

which makes it fail as OpenAI does not like this...

The workaround I found is to delete the function field from the models as soon as I import them

from marvin.beta.assistants import Retrieval
del Retrieval.function

Fakamoto avatar Feb 16 '24 23:02 Fakamoto

I would imagine a better solution would be to modify the Tool model so it does not include the "function" field and name it BaseTool, make RetrievalTool and CodeInterpreterTool inherit from BaseTool. Then create a Tool model that inherits from BaseTool and adds the "function" field

Fakamoto avatar Feb 16 '24 23:02 Fakamoto