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

Refactor manual pydantics for scanpy pl agents #255

Open
wants to merge 9 commits into
base: biohackathon3
Choose a base branch
from

Conversation

mengerj
Copy link

@mengerj mengerj commented Dec 12, 2024

Refactored pydantic class definitions in pl modules to match new structure.
#245

@mengerj mengerj marked this pull request as ready for review December 12, 2024 17:13
@slobentanzer
Copy link
Contributor

@mengerj looks good but doesn't run, I get a string matching error:

/Users/slobentanzer/GitHub/tmp/biochatter/benchmark/test_api_calling.py::test_python_api_calling[gpt-4o-2024-08-06-test_data_api_calling6] failed: model_name = 'gpt-4o-2024-08-06'
test_data_api_calling = {'case': 'scanpy:pl:scatter:exact_variable_names', 'expected': {'parts_of_query': ['sc.pl.scatter\\(', 'n_genes_by_cou...: 'ca2e911fc981986c9f241d35f3a4aa58', 'input': {'prompt': 'Make a scatter plot of n_genes_by_counts vs total_counts.'}}
conversation = <biochatter.llm_connect.GptConversation object at 0x35ae1d480>
multiple_testing = <function multiple_testing.<locals>.run_multiple_times at 0x35ae5fe20>

    def test_python_api_calling(
        model_name,
        test_data_api_calling,
        conversation,
        multiple_testing,
    ):
        """Test the Python API calling capability."""
        task = f"{inspect.currentframe().f_code.co_name.replace('test_', '')}"
        yaml_data = test_data_api_calling
    
        skip_if_already_run(
            model_name=model_name,
            task=task,
            md5_hash=yaml_data["hash"],
        )
    
        if "scanpy" not in yaml_data["case"] and "anndata" not in yaml_data["case"]:
            pytest.skip(
                "Function to be tested is not a Python API",
            )
    
        def run_test():
            conversation.reset()  # needs to be reset for each test
            if "scanpy:pl" in yaml_data["case"]:
                builder = ScanpyPlQueryBuilder()
            elif "anndata" in yaml_data["case"]:
                builder = AnnDataIOQueryBuilder()
            parameters = builder.parameterise_query(
                question=yaml_data["input"]["prompt"],
                conversation=conversation,
            )
    
            method_call = format_as_python_call(parameters[0])
    
            score = []
            for expected_part in ensure_iterable(
                yaml_data["expected"]["parts_of_query"],
            ):
                if re.search(expected_part, method_call):
                    score.append(True)
                else:
                    score.append(False)
    
            return calculate_bool_vector_score(score)
    
>       mean_score, max, n_iterations = multiple_testing(run_test)

benchmark/test_api_calling.py:121: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
benchmark/conftest.py:337: in run_multiple_times
    score, max = test_func(*args, **kwargs)
benchmark/test_api_calling.py:103: in run_test
    parameters = builder.parameterise_query(
biochatter/api_agent/scanpy_pl.py:427: in parameterise_query
    return runnable.invoke(question)
.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py:2877: in invoke
    input = context.run(step.invoke, input, config, **kwargs)
.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py:5093: in invoke
    return self.bound.invoke(
.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py:277: in invoke
    self.generate_prompt(
.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py:777: in generate_prompt
    return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py:634: in generate
    raise e
.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py:624: in generate
    self._generate_with_cache(
.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py:846: in _generate_with_cache
    result = self._generate(
.venv/lib/python3.10/site-packages/langchain_openai/chat_models/base.py:601: in _generate
    response = self.client.create(**payload)
.venv/lib/python3.10/site-packages/openai/_utils/_utils.py:277: in wrapper
    return func(*args, **kwargs)
.venv/lib/python3.10/site-packages/openai/resources/chat/completions.py:646: in create
    return self._post(
.venv/lib/python3.10/site-packages/openai/_base_client.py:1271: in post
    return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
.venv/lib/python3.10/site-packages/openai/_base_client.py:942: in request
    return self._request(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <openai.OpenAI object at 0x35ae53c40>

    def _request(
        self,
        *,
        cast_to: Type[ResponseT],
        options: FinalRequestOptions,
        remaining_retries: int | None,
        stream: bool,
        stream_cls: type[_StreamT] | None,
    ) -> ResponseT | _StreamT:
        # create a copy of the options we were given so that if the
        # options are mutated later & we then retry, the retries are
        # given the original options
        input_options = model_copy(options)
    
        cast_to = self._maybe_override_cast_to(cast_to, options)
        options = self._prepare_options(options)
    
        retries = self._remaining_retries(remaining_retries, options)
        request = self._build_request(options)
        self._prepare_request(request)
    
        kwargs: HttpxSendArgs = {}
        if self.custom_auth is not None:
            kwargs["auth"] = self.custom_auth
    
        log.debug("Sending HTTP Request: %s %s", request.method, request.url)
    
        try:
            response = self._client.send(
                request,
                stream=stream or self._should_stream_response_body(request=request),
                **kwargs,
            )
        except httpx.TimeoutException as err:
            log.debug("Encountered httpx.TimeoutException", exc_info=True)
    
            if retries > 0:
                return self._retry_request(
                    input_options,
                    cast_to,
                    retries,
                    stream=stream,
                    stream_cls=stream_cls,
                    response_headers=None,
                )
    
            log.debug("Raising timeout error")
            raise APITimeoutError(request=request) from err
        except Exception as err:
            log.debug("Encountered Exception", exc_info=True)
    
            if retries > 0:
                return self._retry_request(
                    input_options,
                    cast_to,
                    retries,
                    stream=stream,
                    stream_cls=stream_cls,
                    response_headers=None,
                )
    
            log.debug("Raising connection error")
            raise APIConnectionError(request=request) from err
    
        log.debug(
            'HTTP Response: %s %s "%i %s" %s',
            request.method,
            request.url,
            response.status_code,
            response.reason_phrase,
            response.headers,
        )
        log.debug("request_id: %s", response.headers.get("x-request-id"))
    
        try:
            response.raise_for_status()
        except httpx.HTTPStatusError as err:  # thrown on 4xx and 5xx status code
            log.debug("Encountered httpx.HTTPStatusError", exc_info=True)
    
            if retries > 0 and self._should_retry(err.response):
                err.response.close()
                return self._retry_request(
                    input_options,
                    cast_to,
                    retries,
                    err.response.headers,
                    stream=stream,
                    stream_cls=stream_cls,
                )
    
            # If the response is streamed then we need to explicitly read the response
            # to completion before attempting to access the response text.
            if not err.response.is_closed:
                err.response.read()
    
            log.debug("Re-raising status error")
>           raise self._make_status_error_from_response(err.response) from None
E           openai.BadRequestError: Error code: 400 - {'error': {'message': "Invalid 'tools[0].function.name': string does not match pattern. Expected a string that matches the pattern '^[a-zA-Z0-9_-]+$'.", 'type': 'invalid_request_error', 'param': 'tools[0].function.name', 'code': 'invalid_value'}}

.venv/lib/python3.10/site-packages/openai/_base_client.py:1046: BadRequestError

@mengerj
Copy link
Author

mengerj commented Dec 13, 2024

function names cannot contain "."

@mengerj mengerj requested a review from slobentanzer December 13, 2024 16:52
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.

2 participants