diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8db2a88 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,51 @@ +name: Tests +on: [push, pull_request, workflow_call, workflow_dispatch] + +jobs: + + tests: + name: ✅ Run tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["ubuntu-latest"] + python-version: ["3.9", "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install hatch + + - name: Test with coverage + run: | + hatch run test + + + codeql: + name: 🔎 CodeQL analysis + runs-on: ubuntu-latest + permissions: + security-events: write + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: python + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:python" diff --git a/Dockerfile b/Dockerfile index 50d3270..65c1190 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,8 +13,8 @@ RUN pip install --upgrade pip COPY . /app/ # COPY ./scripts/prestart.sh /app/ -RUN pip install -e ".[cpu]" +RUN pip install -e "." ENV PYTHONPATH=/app -ENV MODULE_NAME=src.expasy_chat.api +ENV MODULE_NAME=src.sparql_llm.api # ENV VARIABLE_NAME=app diff --git a/README.md b/README.md index 0a9d478..472aac7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # 🦜✨ LLM for SPARQL query generation +Reusable components and complete webapp to improve Large Language Models (LLMs) capabilities when generating [SPARQL](https://www.w3.org/TR/sparql11-overview/) queries for a given set of endpoints, using Retrieval-Augmented Generation (RAG) and query validation from the endpoint schema. + +The different components of the system can be used separately, or the whole chat system webapp can be deployed for a set of endpoints. It relies on the endpoint containing some descriptive metadata: [SPARQL query examples](https://github.com/sib-swiss/sparql-examples), and endpoint description using the [Vocabulary of Interlinked Datasets (VoID)](https://www.w3.org/TR/void/), which can generated automatically using the [void-generator](https://github.com/JervenBolleman/void-generator). + This repository contains: -* Utilities and functions to improve LLMs capabilities when working with [SPARQL](https://www.w3.org/TR/sparql11-overview/) endpoints and [RDF](https://www.w3.org/RDF/) knowledge graph. In particular improving SPARQL query generation. - * Loaders are compatible with [LangChain](https://python.langchain.com), but they can also be used outside of LangChain as they just return a list of documents with metadata as JSON, which can then be loaded how you want in your vectorstore. -* A complete reusable system to deploy a LLM chat system for multiple SPARQL endpoints (WIP) -* The deployment for **[chat.expasy.org](https://chat.expasy.org)** the LLM chat system to help users accessing the endpoints maintained at the SIB +* Functions to extract and load relevant metadata from a SPARQL endpoints. Loaders are compatible with [LangChain](https://python.langchain.com), but they can also be used outside of LangChain as they just return a list of documents with metadata as JSON, which can then be loaded how you want in your vectorstore. +* Function to automatically parse and validate SPARQL queries based on a endpoint VoID description. +* A complete reusable system to deploy a LLM chat system with web UI, API and vector database, designed to help users to write SPARQL queries for a given set of endpoints by exploiting metadata uploaded to the endpoints (WIP). +* The deployment configuration for **[chat.expasy.org](https://chat.expasy.org)** the LLM chat system to help users accessing the endpoints maintained at the [SIB](https://www.sib.swiss/). ## 🪄 Reusable components @@ -34,9 +38,11 @@ print(docs[0].metadata) ### SPARQL endpoint schema loader -Generate a human-readable schema using the ShEx format to describe all classes of a SPARQL endpoint based on its [VoID description](https://www.w3.org/TR/void/) present in your endpoint. Ideally the endpoint should also contain the ontology describing the class, so the `rdfs:label` and `rdfs:comment` of the class can be used to generate embeddings and improve semantic matching. +Generate a human-readable schema using the ShEx format to describe all classes of a SPARQL endpoint based on the [VoID description](https://www.w3.org/TR/void/) present in the endpoint. Ideally the endpoint should also contain the ontology describing the class, so the `rdfs:label` and `rdfs:comment` of the classes can be used to generate embeddings and improve semantic matching. -Checkout the **[void-generator](https://github.com/JervenBolleman/void-generator)** project to automatically generate VoID description for your endpoint. +> [!TIP] +> +> Checkout the **[void-generator](https://github.com/JervenBolleman/void-generator)** project to automatically generate VoID description for your endpoint. ```python from sparql_llm import SparqlVoidShapesLoader @@ -64,25 +70,25 @@ This takes a SPARQL query and validates the predicates/types used are compliant This function supports: -* federated queries (VoID description will be retrieved for each SERVICE call), +* federated queries (VoID description will be automatically retrieved for each SERVICE call in the query), * path patterns (e.g. `orth:organism/obo:RO_0002162/up:scientificName`) -The function requires that at least one type is defined for each endpoint, but it will be able to infer types of subjects that are connected to the subject for which the type is defined. +This function requires that at least one type is defined for each endpoint, but it will be able to infer types of subjects that are connected to the subject for which the type is defined. -It will return a list of issues described in natural language, with hints on how to fix them (by listing the available classes or predicates in the context), which can be passed to an LLM to help for fixing the query. +It will return a list of issues described in natural language, with hints on how to fix them (by listing the available classes/predicates), which can be passed to an LLM as context to help it figuring out how to fix the query. ```python from sparql_llm import validate_sparql_with_void sparql_query = """PREFIX skos: -PREFIX up: -PREFIX taxon: -PREFIX rdfs: -PREFIX orth: -PREFIX dcterms: -PREFIX obo: -PREFIX lscr: -PREFIX genex: +PREFIX up: +PREFIX taxon: +PREFIX rdfs: +PREFIX orth: +PREFIX dcterms: +PREFIX obo: +PREFIX lscr: +PREFIX genex: PREFIX sio: SELECT DISTINCT ?diseaseLabel ?humanProtein ?hgncSymbol ?orthologRatProtein ?orthologRatGene WHERE { @@ -122,20 +128,19 @@ WHERE { ?anatEntity rdfs:label 'brain' . ?ratOrganism obo:RO_0002162 taxon:10116 . } -} -""" +}""" issues = validate_sparql_with_void(sparql_query, "https://sparql.uniprot.org/sparql/") print("\n".join(issues)) ``` -## 🚀 Deploy chat system +## 🚀 Complete chat system > [!WARNING] > > To deploy the complete chat system right now you will need to fork this repository, change the configuration in `src/sparql_llm/config.py` and `compose.yml`, then deploy with docker/podman compose. > -> We plan to make configuration and deployment of complete SPARQL LLM chat system easier in the future, let us know if you are interested in the GitHub issues! +> It can easily be adapted to use any LLM served through an OpenAI-compatible API. We plan to make configuration and deployment of complete SPARQL LLM chat system easier in the future, let us know if you are interested in the GitHub issues! Create a `.env` file at the root of the repository to provide OpenAI API key to a `.env` file at the root of the repository: @@ -143,18 +148,21 @@ Create a `.env` file at the root of the repository to provide OpenAI API key to OPENAI_API_KEY=sk-proj-YYY GLHF_API_KEY=APIKEY_FOR_glhf.chat_USED_FOR_OPEN_SOURCE_MODELS EXPASY_API_KEY=NOT_SO_SECRET_API_KEY_USED_BY_FRONTEND_TO_AVOID_SPAM_FROM_CRAWLERS -LOGS_API_KEY=PASSWORD_TO_ACCESS_LOGS_THROUGH_THE_API +LOGS_API_KEY=PASSWORD_TO_EASILY_ACCESS_LOGS_THROUGH_THE_API ``` -Start the web UI, API, and similarity search engine in production (you might need to make some changes to the `compose.yml` file to adapt it to your server setup): +Start the web UI, API, and similarity search engine in production (you might need to make some changes to the `compose.yml` file to adapt it to your server/proxy setup): ```bash docker compose up ``` -Start the stack locally for development: +Start the stack locally for development, with code from `src` folder mounted in the container and automatic API reload on changes to the code: ```bash docker compose -f compose.dev.yml up ``` +* Chat web UI available at http://localhost:8000 +* OpenAPI Swagger UI available at http://localhost:8000/docs +* Vector database dashboard UI available at http://localhost:6333/dashboard diff --git a/compose.dev.yml b/compose.dev.yml index a7aa977..5fa37b5 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -21,6 +21,7 @@ services: - ./prestart.sh:/app/prestart.sh entrypoint: /start-reload.sh + # In case you need a GPU-enabled workspace # workspace: # image: ghcr.io/vemonet/gpu-workspace:main # # Enable GPUs in this container: diff --git a/compose.yml b/compose.yml index 92b0c7c..8c2f4e9 100644 --- a/compose.yml +++ b/compose.yml @@ -2,7 +2,6 @@ services: vectordb: # https://hub.docker.com/r/qdrant/qdrant/tags - # image: docker.io/qdrant/qdrant:v1.9.5 image: docker.io/qdrant/qdrant:v1.11.3 # image: qdrant/qdrant:v1.9.2-unprivileged # Unprivileged don't work when mounting a volume container_name: vectordb @@ -12,11 +11,6 @@ services: # - ./qdrant_config.yml:/qdrant/config/production.yaml environment: - QDRANT_ALLOW_RECOVERY_MODE=true - # networks: - # - default - # ports: - # - 6333:6333 - # - 6334:6334 # command: # - ./qdrant --config-path /qdrant/config/production.yaml @@ -34,21 +28,13 @@ services: - ./data/fastembed_cache:/tmp/fastembed_cache - ./data/logs:/logs - ./src:/app/src - # entrypoint: uvicorn src.expasy_chat.api:app --host 0.0.0.0 --port 80 + # entrypoint: uvicorn src.sparql_llm.api:app --host 0.0.0.0 --port 80 env_file: - .env - # networks: - # - default # TODO: add ollama -# networks: -# default: -# driver: pasta -# # driver: bridge - - # podman-compose down && podman network prune -f -# podman exec -it expasy-chat_api_1 bash -c "apt-get update && apt-get install -y telnet && telnet vectordb 6334" +# podman exec -it sparql-llm_api_1 bash -c "apt-get update && apt-get install -y telnet && telnet vectordb 6334" # < /dev/tcp/vectordb/6334 -# podman exec -it api bash -c "< /dev/tcp/vectordb/6334" \ No newline at end of file +# podman exec -it api bash -c "< /dev/tcp/vectordb/6334" diff --git a/deploy.sh b/deploy.sh index c33dc1f..fd4b3b2 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,13 +1,13 @@ if [ "$1" = "--build" ]; then echo "📦️ Re-building" - ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/expasy-chat ; git pull ; podman-compose up --force-recreate --build -d"' + ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/sparql-llm ; git pull ; podman-compose up --force-recreate --build -d"' elif [ "$1" = "--logs" ]; then - ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/expasy-chat ; podman-compose logs api"' + ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/sparql-llm ; podman-compose logs api"' elif [ "$1" = "--likes" ]; then mkdir -p data/prod - scp expasychat:/var/containers/podman/expasy-chat/data/logs/likes.jsonl ./data/prod/ - scp expasychat:/var/containers/podman/expasy-chat/data/logs/dislikes.jsonl ./data/prod/ - scp expasychat:/var/containers/podman/expasy-chat/data/logs/user_questions.log ./data/prod/ + scp expasychat:/var/containers/podman/sparql-llm/data/logs/likes.jsonl ./data/prod/ + scp expasychat:/var/containers/podman/sparql-llm/data/logs/dislikes.jsonl ./data/prod/ + scp expasychat:/var/containers/podman/sparql-llm/data/logs/user_questions.log ./data/prod/ else - ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/expasy-chat ; git pull ; podman-compose up --force-recreate -d"' + ssh expasychat 'sudo -u podman bash -c "cd /var/containers/podman/sparql-llm ; git pull ; podman-compose up --force-recreate -d"' fi diff --git a/notebooks/compare_queries_examples_to_void.ipynb b/notebooks/compare_queries_examples_to_void.ipynb index 4ab0839..82c8773 100644 --- a/notebooks/compare_queries_examples_to_void.ipynb +++ b/notebooks/compare_queries_examples_to_void.ipynb @@ -356,9 +356,9 @@ "source": [ "from qdrant_client.models import FieldCondition, Filter, MatchValue\n", "\n", - "from expasy_chat.config import settings\n", - "from expasy_chat.embed import get_vectordb\n", - "from expasy_chat.validate_sparql import get_void_dict, sparql_query_to_dict\n", + "from sparql_llm.config import settings\n", + "from sparql_llm.embed import get_vectordb\n", + "from sparql_llm.validate_sparql import get_void_dict, sparql_query_to_dict\n", "\n", "check_endpoints = {\n", " \"UniProt\": \"https://sparql.uniprot.org/sparql/\",\n", diff --git a/notebooks/compute_stats_on_example_queries.ipynb b/notebooks/compute_stats_on_example_queries.ipynb index deebc18..8f69482 100644 --- a/notebooks/compute_stats_on_example_queries.ipynb +++ b/notebooks/compute_stats_on_example_queries.ipynb @@ -45,7 +45,7 @@ "import pandas as pd\n", "from rdflib import Graph\n", "\n", - "from expasy_chat.validate_sparql import sparql_query_to_dict\n", + "from sparql_llm.validate_sparql import sparql_query_to_dict\n", "\n", "GET_EXAMPLE_QUERY = \"\"\"PREFIX sh: \n", "PREFIX schema: \n", diff --git a/notebooks/get_shex_from_void.ipynb b/notebooks/get_shex_from_void.ipynb index 886b290..0359922 100644 --- a/notebooks/get_shex_from_void.ipynb +++ b/notebooks/get_shex_from_void.ipynb @@ -1504,7 +1504,7 @@ } ], "source": [ - "from expasy_chat.void_to_shex import get_shex_dict_from_void, get_shex_from_void\n", + "from sparql_llm.void_to_shex import get_shex_dict_from_void, get_shex_from_void\n", "\n", "shex_dict = get_shex_dict_from_void(\"https://sparql.uniprot.org/sparql/\")\n", "print(len(shex_dict))\n", diff --git a/notebooks/parse_sparql.ipynb b/notebooks/parse_sparql.ipynb index 57c96de..35b5eb2 100644 --- a/notebooks/parse_sparql.ipynb +++ b/notebooks/parse_sparql.ipynb @@ -18,15 +18,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "Subject ?pathVar5 in endpoint https://sparql.omabrowser.org/sparql/ does not support the predicate up:scientificNam. Correct predicate might be one of the following: up:rank, orth:taxRangeId, up:mnemonic, orth:taxRange, up:scientificName (we inferred this variable might be of the type orth:TaxonomicRange)\n", - "Subject ?hgnc in endpoint https://sparql.uniprot.org/sparql does not support the predicate rdfs:label. Correct predicate might be one of the following: owl:sameAs, rdfs:seeAlso, up:transcribedFrom, up:translatedTo, rdfs:comment, up:database (we inferred this variable might be of the type up:Transcript_Resource)\n", "Subject ?orthologRatProtein with type orth:Protein in endpoint https://sparql.omabrowser.org/sparql/ does not support the predicate sio:SIO_010078. It can have the following predicates: lscr:xrefUniprot, orth:organism, obo:RO_0001018, lscr:xrefEnsemblProtein, lscr:xrefEnsemblTranscript, sio:SIO_010079, lscr:xrefSwissProt, rdfs:label, dc:identifier, rdfs:comment, lscr:xrefNCBIRefSeq, lscr:xrefNCBIProtein, rdfs:seeAlso, lscr:xrefEMBLSequence\n", - "Subject ?disease with type up:Disease in endpoint https://sparql.uniprot.org/sparql does not support the predicate rdfs:label. It can have the following predicates: skos:altLabel, rdfs:comment, up:mnemonic, skos:prefLabel, rdfs:seeAlso\n" + "Subject ?hgnc in endpoint https://sparql.uniprot.org/sparql does not support the predicate rdfs:label. Correct predicate might be one of the following: owl:sameAs, rdfs:seeAlso, up:transcribedFrom, up:translatedTo, rdfs:comment, up:database (we inferred this variable might be of the type up:Transcript_Resource)\n", + "Subject ?disease with type up:Disease in endpoint https://sparql.uniprot.org/sparql does not support the predicate rdfs:label. It can have the following predicates: skos:altLabel, rdfs:comment, up:mnemonic, skos:prefLabel, rdfs:seeAlso\n", + "Subject ?pathVar5 in endpoint https://sparql.omabrowser.org/sparql/ does not support the predicate up:scientificNam. Correct predicate might be one of the following: up:rank, orth:taxRangeId, up:mnemonic, orth:taxRange, up:scientificName (we inferred this variable might be of the type orth:TaxonomicRange)\n" ] } ], "source": [ - "from expasy_chat.validate_sparql import validate_sparql_with_void\n", + "from sparql_llm.validate_sparql import validate_sparql_with_void\n", "\n", "# sparql_query = \"\"\"\n", "# PREFIX up: \n", @@ -224,7 +224,7 @@ "source": [ "import json\n", "\n", - "from expasy_chat.validate_sparql import sparql_query_to_dict\n", + "from sparql_llm.validate_sparql import sparql_query_to_dict\n", "\n", "path_query = \"\"\"PREFIX rdfs: \n", "PREFIX up: \n", @@ -249,231 +249,31 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [ - { - "ename": "ParseException", - "evalue": "Expected {SelectQuery | ConstructQuery | DescribeQuery | AskQuery} (at char 0), (line:1, col:1)", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mParseException\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[3], line 60\u001b[0m\n\u001b[1;32m 57\u001b[0m triples\u001b[38;5;241m.\u001b[39mextend(extract_triples(item))\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m triples\n\u001b[0;32m---> 60\u001b[0m sq \u001b[38;5;241m=\u001b[39m \u001b[43mprepareQuery\u001b[49m\u001b[43m(\u001b[49m\u001b[43msparql_query\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 61\u001b[0m \u001b[38;5;28mprint\u001b[39m(sq\u001b[38;5;241m.\u001b[39malgebra\u001b[38;5;241m.\u001b[39mname)\n\u001b[1;32m 62\u001b[0m \u001b[38;5;28mprint\u001b[39m(prepareQuery(toast_query)\u001b[38;5;241m.\u001b[39malgebra)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/processor.py:31\u001b[0m, in \u001b[0;36mprepareQuery\u001b[0;34m(queryString, initNs, base)\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m initNs \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 30\u001b[0m initNs \u001b[38;5;241m=\u001b[39m {}\n\u001b[0;32m---> 31\u001b[0m ret \u001b[38;5;241m=\u001b[39m translateQuery(\u001b[43mparseQuery\u001b[49m\u001b[43m(\u001b[49m\u001b[43mqueryString\u001b[49m\u001b[43m)\u001b[49m, base, initNs)\n\u001b[1;32m 32\u001b[0m ret\u001b[38;5;241m.\u001b[39m_original_args \u001b[38;5;241m=\u001b[39m (queryString, initNs, base)\n\u001b[1;32m 33\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ret\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/parser.py:1542\u001b[0m, in \u001b[0;36mparseQuery\u001b[0;34m(q)\u001b[0m\n\u001b[1;32m 1539\u001b[0m q \u001b[38;5;241m=\u001b[39m q\u001b[38;5;241m.\u001b[39mdecode(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 1541\u001b[0m q \u001b[38;5;241m=\u001b[39m expandUnicodeEscapes(q)\n\u001b[0;32m-> 1542\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mQuery\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseString\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparseAll\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/util.py:256\u001b[0m, in \u001b[0;36mreplaced_by_pep8.._inner\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 252\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_inner\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 253\u001b[0m \u001b[38;5;66;03m# warnings.warn(\u001b[39;00m\n\u001b[1;32m 254\u001b[0m \u001b[38;5;66;03m# f\"Deprecated - use {fn.__name__}\", DeprecationWarning, stacklevel=2\u001b[39;00m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# )\u001b[39;00m\n\u001b[0;32m--> 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1199\u001b[0m, in \u001b[0;36mParserElement.parse_string\u001b[0;34m(self, instring, parse_all, parseAll)\u001b[0m\n\u001b[1;32m 1196\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n\u001b[1;32m 1197\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1198\u001b[0m \u001b[38;5;66;03m# catch and re-raise exception from here, clearing out pyparsing internal stack trace\u001b[39;00m\n\u001b[0;32m-> 1199\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\u001b[38;5;241m.\u001b[39mwith_traceback(\u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[1;32m 1200\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1201\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m tokens\n", - "\u001b[0;31mParseException\u001b[0m: Expected {SelectQuery | ConstructQuery | DescribeQuery | AskQuery} (at char 0), (line:1, col:1)" - ] - } - ], + "outputs": [], "source": [ - "from rdflib.plugins.sparql import prepareQuery\n", - "from rdflib.plugins.sparql.parser import parseQuery\n", - "\n", - "toast_query = \"\"\"PREFIX : \n", - "PREFIX cco: \n", - "PREFIX rdf: \n", - "PREFIX rdfs: \n", - "PREFIX sachem: \n", - "PREFIX skos: \n", - "\n", - "SELECT distinct ?entry (group_concat(distinct str(?gomflab); SEPARATOR = \",\") as ?gomfx) WHERE {\n", - "\tSERVICE {\n", - "\t\tSERVICE {\n", - "\t\t ?compound sachem:substructureSearch [ sachem:query \"CC12CCC3C(C1CCC2O)CCC4=C3C=CC(=C4)O\" ] . # smiles chain for estradiol\n", - "\t\t}\n", - "\t\t?ACTIVITY rdf:type cco:Activity;\n", - "\t\tcco:hasMolecule ?compound;\n", - "\t\tcco:hasAssay ?ASSAY.\n", - "\t\t?ASSAY cco:hasTarget ?TARGET.\n", - "\t\t?TARGET cco:hasTargetComponent ?COMPONENT.\n", - "\t\t?TARGET cco:taxonomy . # human protein target\n", - "\t\t?COMPONENT cco:targetCmptXref ?UNIPROT.\n", - "\t\t#?UNIPROT rdf:type cco:UniprotRef.\n", - "\t\tfilter(contains(str(?UNIPROT),\"uniprot\"))\n", - "\t}\n", + "# from rdflib.plugins.sparql import prepareQuery\n", "\n", - "\t?entry skos:exactMatch ?UNIPROT.\n", - "\t?entry :isoform ?iso.\n", - "\t?iso :goMolecularFunction / :term ?gomf .\n", - "\t?gomf rdfs:label ?gomflab .\n", - "}\"\"\"\n", - "\n", - "\n", - "def extract_triples(node):\n", - " \"\"\"\n", - " Extract triples from SPARQL query\n", - " q = prepareQuery(sparql_text)\n", - " triples = extract_triples(q.algebra)\n", - "\n", - " Args:\n", - " nodes : q.algebra\n", - " Returns:\n", - " list of triples\n", - " \"\"\"\n", - " triples = []\n", - " # print(type(node))\n", - " if isinstance(node, dict):\n", - " # print(\"IS DICT!\", node)\n", - " for key, value in node.items():\n", - " if key == \"triples\":\n", - " triples.extend(value)\n", - " else:\n", - " triples.extend(extract_triples(value))\n", - " elif isinstance(node, list):\n", - " # print(\"IS LIST!\", node)\n", - " for item in node:\n", - " triples.extend(extract_triples(item))\n", - " return triples\n", - "\n", - "\n", - "sq = prepareQuery(sparql_query)\n", - "print(sq.algebra.name)\n", - "print(prepareQuery(toast_query).algebra)\n", - "triples = extract_triples(prepareQuery(sparql_query).algebra)\n", - "\n", - "print(triples)\n", - "print(len(triples))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "RecursionError", - "evalue": "maximum recursion depth exceeded", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 24\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mrdflib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mplugins\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01msparql\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mparser\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m parseQuery\n\u001b[1;32m 5\u001b[0m query \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;124mPREFIX wikibase: \u001b[39m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;124mPREFIX wdt: \u001b[39m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 22\u001b[0m \n\u001b[1;32m 23\u001b[0m \u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[0;32m---> 24\u001b[0m parsed_query \u001b[38;5;241m=\u001b[39m \u001b[43mparseQuery\u001b[49m\u001b[43m(\u001b[49m\u001b[43mquery\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 25\u001b[0m \u001b[38;5;66;03m# print(sq.algebra.name)\u001b[39;00m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;66;03m# print(prepareQuery(toast_query).algebra)\u001b[39;00m\n\u001b[1;32m 27\u001b[0m \u001b[38;5;66;03m# triples = extract_triples(prepareQuery(sparql_query).algebra)\u001b[39;00m\n\u001b[1;32m 28\u001b[0m \n\u001b[1;32m 29\u001b[0m \u001b[38;5;66;03m# print(triples)\u001b[39;00m\n\u001b[1;32m 30\u001b[0m \u001b[38;5;66;03m# print(len(triples))\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/parser.py:1542\u001b[0m, in \u001b[0;36mparseQuery\u001b[0;34m(q)\u001b[0m\n\u001b[1;32m 1539\u001b[0m q \u001b[38;5;241m=\u001b[39m q\u001b[38;5;241m.\u001b[39mdecode(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 1541\u001b[0m q \u001b[38;5;241m=\u001b[39m expandUnicodeEscapes(q)\n\u001b[0;32m-> 1542\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mQuery\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseString\u001b[49m\u001b[43m(\u001b[49m\u001b[43mq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparseAll\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/util.py:256\u001b[0m, in \u001b[0;36mreplaced_by_pep8.._inner\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 252\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_inner\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 253\u001b[0m \u001b[38;5;66;03m# warnings.warn(\u001b[39;00m\n\u001b[1;32m 254\u001b[0m \u001b[38;5;66;03m# f\"Deprecated - use {fn.__name__}\", DeprecationWarning, stacklevel=2\u001b[39;00m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# )\u001b[39;00m\n\u001b[0;32m--> 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1189\u001b[0m, in \u001b[0;36mParserElement.parse_string\u001b[0;34m(self, instring, parse_all, parseAll)\u001b[0m\n\u001b[1;32m 1187\u001b[0m instring \u001b[38;5;241m=\u001b[39m instring\u001b[38;5;241m.\u001b[39mexpandtabs()\n\u001b[1;32m 1188\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 1189\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1190\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m parseAll:\n\u001b[1;32m 1191\u001b[0m loc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpreParse(instring, loc)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4080\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4076\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseSyntaxException(\n\u001b[1;32m 4077\u001b[0m instring, \u001b[38;5;28mlen\u001b[39m(instring), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 4078\u001b[0m )\n\u001b[1;32m 4079\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 4080\u001b[0m loc, exprtokens \u001b[38;5;241m=\u001b[39m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4081\u001b[0m resultlist \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m exprtokens\n\u001b[1;32m 4082\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, resultlist\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4080\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4076\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseSyntaxException(\n\u001b[1;32m 4077\u001b[0m instring, \u001b[38;5;28mlen\u001b[39m(instring), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 4078\u001b[0m )\n\u001b[1;32m 4079\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 4080\u001b[0m loc, exprtokens \u001b[38;5;241m=\u001b[39m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4081\u001b[0m resultlist \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m exprtokens\n\u001b[1;32m 4082\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, resultlist\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5532\u001b[0m, in \u001b[0;36mForward.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5527\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 5528\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mForward expression was never assigned a value, will not parse any input\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 5529\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mstacklevel,\n\u001b[1;32m 5530\u001b[0m )\n\u001b[1;32m 5531\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39m_left_recursion_enabled:\n\u001b[0;32m-> 5532\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5533\u001b[0m \u001b[38;5;66;03m# ## Bounded Recursion algorithm ##\u001b[39;00m\n\u001b[1;32m 5534\u001b[0m \u001b[38;5;66;03m# Recursion only needs to be processed at ``Forward`` elements, since they are\u001b[39;00m\n\u001b[1;32m 5535\u001b[0m \u001b[38;5;66;03m# the only ones that can actually refer to themselves. The general idea is\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 5550\u001b[0m \u001b[38;5;66;03m# we expand using `do_actions=False` and only run `do_actions=True` if the next\u001b[39;00m\n\u001b[1;32m 5551\u001b[0m \u001b[38;5;66;03m# recursion level is acceptable.\u001b[39;00m\n\u001b[1;32m 5552\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mrecursion_lock:\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (2 times), ParseElementEnhance.parseImpl at line 4578 (1 times), And.parseImpl at line 4080 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (3 times), ParseElementEnhance.parseImpl at line 4578 (1 times), And.parseImpl at line 4080 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5105\u001b[0m, in \u001b[0;36mZeroOrMore.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5103\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m ParseImplReturnType:\n\u001b[1;32m 5104\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 5105\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5106\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ParseException, \u001b[38;5;167;01mIndexError\u001b[39;00m):\n\u001b[1;32m 5107\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, ParseResults([], name\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5004\u001b[0m, in \u001b[0;36m_MultipleMatch.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5002\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m check_ender:\n\u001b[1;32m 5003\u001b[0m try_not_ender(instring, loc)\n\u001b[0;32m-> 5004\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mself_expr_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5005\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 5006\u001b[0m hasIgnoreExprs \u001b[38;5;241m=\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mignoreExprs\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (6 times), ParseElementEnhance.parseImpl at line 4578 (3 times), MatchFirst.parseImpl at line 4309 (1 times), And.parseImpl at line 4080 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5532\u001b[0m, in \u001b[0;36mForward.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5527\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 5528\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mForward expression was never assigned a value, will not parse any input\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 5529\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mstacklevel,\n\u001b[1;32m 5530\u001b[0m )\n\u001b[1;32m 5531\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39m_left_recursion_enabled:\n\u001b[0;32m-> 5532\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5533\u001b[0m \u001b[38;5;66;03m# ## Bounded Recursion algorithm ##\u001b[39;00m\n\u001b[1;32m 5534\u001b[0m \u001b[38;5;66;03m# Recursion only needs to be processed at ``Forward`` elements, since they are\u001b[39;00m\n\u001b[1;32m 5535\u001b[0m \u001b[38;5;66;03m# the only ones that can actually refer to themselves. The general idea is\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 5550\u001b[0m \u001b[38;5;66;03m# we expand using `do_actions=False` and only run `do_actions=True` if the next\u001b[39;00m\n\u001b[1;32m 5551\u001b[0m \u001b[38;5;66;03m# recursion level is acceptable.\u001b[39;00m\n\u001b[1;32m 5552\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mrecursion_lock:\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (5 times), ParseElementEnhance.parseImpl at line 4578 (2 times), And.parseImpl at line 4080 (2 times), MatchFirst.parseImpl at line 4309 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5105\u001b[0m, in \u001b[0;36mZeroOrMore.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5103\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m ParseImplReturnType:\n\u001b[1;32m 5104\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 5105\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5106\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ParseException, \u001b[38;5;167;01mIndexError\u001b[39;00m):\n\u001b[1;32m 5107\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, ParseResults([], name\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5004\u001b[0m, in \u001b[0;36m_MultipleMatch.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5002\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m check_ender:\n\u001b[1;32m 5003\u001b[0m try_not_ender(instring, loc)\n\u001b[0;32m-> 5004\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mself_expr_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5005\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 5006\u001b[0m hasIgnoreExprs \u001b[38;5;241m=\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mignoreExprs\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (2 times), ParseElementEnhance.parseImpl at line 4578 (1 times), MatchFirst.parseImpl at line 4309 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:857\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 854\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 855\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseImpl(instring, pre_loc, do_actions)\n\u001b[0;32m--> 857\u001b[0m tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpostParse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtokens\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 859\u001b[0m ret_tokens \u001b[38;5;241m=\u001b[39m ParseResults(\n\u001b[1;32m 860\u001b[0m tokens, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName, asList\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msaveAsList, modal\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodalResults\n\u001b[1;32m 861\u001b[0m )\n\u001b[1;32m 862\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseAction \u001b[38;5;129;01mand\u001b[39;00m (do_actions \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcallDuringTry):\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/parserutils.py:264\u001b[0m, in \u001b[0;36mComp.postParse\u001b[0;34m(self, instring, loc, tokenList)\u001b[0m\n\u001b[1;32m 259\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mServiceGraphPattern\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 260\u001b[0m \u001b[38;5;66;03m# Then this must be a service graph pattern and have\u001b[39;00m\n\u001b[1;32m 261\u001b[0m \u001b[38;5;66;03m# already matched.\u001b[39;00m\n\u001b[1;32m 262\u001b[0m \u001b[38;5;66;03m# lets assume there is one, for now, then test for two later.\u001b[39;00m\n\u001b[1;32m 263\u001b[0m sgp \u001b[38;5;241m=\u001b[39m originalTextFor(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexpr)\n\u001b[0;32m--> 264\u001b[0m service_string \u001b[38;5;241m=\u001b[39m \u001b[43msgp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msearchString\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m)\u001b[49m[\u001b[38;5;241m0\u001b[39m][\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 265\u001b[0m res[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mservice_string\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m service_string\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m t \u001b[38;5;129;01min\u001b[39;00m tokenList:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/util.py:256\u001b[0m, in \u001b[0;36mreplaced_by_pep8.._inner\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 252\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_inner\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 253\u001b[0m \u001b[38;5;66;03m# warnings.warn(\u001b[39;00m\n\u001b[1;32m 254\u001b[0m \u001b[38;5;66;03m# f\"Deprecated - use {fn.__name__}\", DeprecationWarning, stacklevel=2\u001b[39;00m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# )\u001b[39;00m\n\u001b[0;32m--> 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36mParserElement.search_string\u001b[0;34m(self, instring, max_matches, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1262\u001b[0m, in \u001b[0;36mParserElement.scan_string\u001b[0;34m(self, instring, max_matches, overlap, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1260\u001b[0m nextLoc: \u001b[38;5;28mint\u001b[39m\n\u001b[1;32m 1261\u001b[0m tokens: ParseResults\n\u001b[0;32m-> 1262\u001b[0m nextLoc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mparseFn\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpreloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 1263\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseException:\n\u001b[1;32m 1264\u001b[0m loc \u001b[38;5;241m=\u001b[39m preloc \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (11 times), ParseElementEnhance.parseImpl at line 4578 (4 times), And.parseImpl at line 4080 (3 times), MatchFirst.parseImpl at line 4309 (2 times), Forward.parseImpl at line 5532 (1 times), ZeroOrMore.parseImpl at line 5105 (1 times), _MultipleMatch.parseImpl at line 5004 (1 times), And.parseImpl at line 4058 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:857\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 854\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 855\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseImpl(instring, pre_loc, do_actions)\n\u001b[0;32m--> 857\u001b[0m tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpostParse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtokens\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 859\u001b[0m ret_tokens \u001b[38;5;241m=\u001b[39m ParseResults(\n\u001b[1;32m 860\u001b[0m tokens, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName, asList\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msaveAsList, modal\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodalResults\n\u001b[1;32m 861\u001b[0m )\n\u001b[1;32m 862\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseAction \u001b[38;5;129;01mand\u001b[39;00m (do_actions \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcallDuringTry):\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/parserutils.py:264\u001b[0m, in \u001b[0;36mComp.postParse\u001b[0;34m(self, instring, loc, tokenList)\u001b[0m\n\u001b[1;32m 259\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mServiceGraphPattern\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 260\u001b[0m \u001b[38;5;66;03m# Then this must be a service graph pattern and have\u001b[39;00m\n\u001b[1;32m 261\u001b[0m \u001b[38;5;66;03m# already matched.\u001b[39;00m\n\u001b[1;32m 262\u001b[0m \u001b[38;5;66;03m# lets assume there is one, for now, then test for two later.\u001b[39;00m\n\u001b[1;32m 263\u001b[0m sgp \u001b[38;5;241m=\u001b[39m originalTextFor(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexpr)\n\u001b[0;32m--> 264\u001b[0m service_string \u001b[38;5;241m=\u001b[39m \u001b[43msgp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msearchString\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m)\u001b[49m[\u001b[38;5;241m0\u001b[39m][\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 265\u001b[0m res[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mservice_string\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m service_string\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m t \u001b[38;5;129;01min\u001b[39;00m tokenList:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/util.py:256\u001b[0m, in \u001b[0;36mreplaced_by_pep8.._inner\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 252\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_inner\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 253\u001b[0m \u001b[38;5;66;03m# warnings.warn(\u001b[39;00m\n\u001b[1;32m 254\u001b[0m \u001b[38;5;66;03m# f\"Deprecated - use {fn.__name__}\", DeprecationWarning, stacklevel=2\u001b[39;00m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# )\u001b[39;00m\n\u001b[0;32m--> 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36mParserElement.search_string\u001b[0;34m(self, instring, max_matches, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1262\u001b[0m, in \u001b[0;36mParserElement.scan_string\u001b[0;34m(self, instring, max_matches, overlap, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1260\u001b[0m nextLoc: \u001b[38;5;28mint\u001b[39m\n\u001b[1;32m 1261\u001b[0m tokens: ParseResults\n\u001b[0;32m-> 1262\u001b[0m nextLoc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mparseFn\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpreloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 1263\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseException:\n\u001b[1;32m 1264\u001b[0m loc \u001b[38;5;241m=\u001b[39m preloc \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1023 times), ParseElementEnhance.parseImpl at line 4578 (372 times), And.parseImpl at line 4080 (279 times), MatchFirst.parseImpl at line 4309 (186 times), Forward.parseImpl at line 5532 (93 times), ZeroOrMore.parseImpl at line 5105 (93 times), _MultipleMatch.parseImpl at line 5004 (93 times), And.parseImpl at line 4058 (93 times), at line 1375 (92 times), replaced_by_pep8.._inner at line 256 (92 times), ParserElement._parseNoCache at line 857 (92 times), Comp.postParse at line 264 (92 times), ParserElement.scan_string at line 1262 (92 times), ParserElement.search_string at line 1375 (92 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:857\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 854\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 855\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseImpl(instring, pre_loc, do_actions)\n\u001b[0;32m--> 857\u001b[0m tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpostParse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtokens\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 859\u001b[0m ret_tokens \u001b[38;5;241m=\u001b[39m ParseResults(\n\u001b[1;32m 860\u001b[0m tokens, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName, asList\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msaveAsList, modal\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodalResults\n\u001b[1;32m 861\u001b[0m )\n\u001b[1;32m 862\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparseAction \u001b[38;5;129;01mand\u001b[39;00m (do_actions \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcallDuringTry):\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/rdflib/plugins/sparql/parserutils.py:264\u001b[0m, in \u001b[0;36mComp.postParse\u001b[0;34m(self, instring, loc, tokenList)\u001b[0m\n\u001b[1;32m 259\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mServiceGraphPattern\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m 260\u001b[0m \u001b[38;5;66;03m# Then this must be a service graph pattern and have\u001b[39;00m\n\u001b[1;32m 261\u001b[0m \u001b[38;5;66;03m# already matched.\u001b[39;00m\n\u001b[1;32m 262\u001b[0m \u001b[38;5;66;03m# lets assume there is one, for now, then test for two later.\u001b[39;00m\n\u001b[1;32m 263\u001b[0m sgp \u001b[38;5;241m=\u001b[39m originalTextFor(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexpr)\n\u001b[0;32m--> 264\u001b[0m service_string \u001b[38;5;241m=\u001b[39m \u001b[43msgp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msearchString\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m)\u001b[49m[\u001b[38;5;241m0\u001b[39m][\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m 265\u001b[0m res[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mservice_string\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m service_string\n\u001b[1;32m 267\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m t \u001b[38;5;129;01min\u001b[39;00m tokenList:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/util.py:256\u001b[0m, in \u001b[0;36mreplaced_by_pep8.._inner\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[38;5;129m@wraps\u001b[39m(fn)\n\u001b[1;32m 252\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_inner\u001b[39m(\u001b[38;5;28mself\u001b[39m, \u001b[38;5;241m*\u001b[39margs, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs):\n\u001b[1;32m 253\u001b[0m \u001b[38;5;66;03m# warnings.warn(\u001b[39;00m\n\u001b[1;32m 254\u001b[0m \u001b[38;5;66;03m# f\"Deprecated - use {fn.__name__}\", DeprecationWarning, stacklevel=2\u001b[39;00m\n\u001b[1;32m 255\u001b[0m \u001b[38;5;66;03m# )\u001b[39;00m\n\u001b[0;32m--> 256\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36mParserElement.search_string\u001b[0;34m(self, instring, max_matches, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1375\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1372\u001b[0m maxMatches \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mmin\u001b[39m(maxMatches, max_matches)\n\u001b[1;32m 1373\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m ParseResults(\n\u001b[0;32m-> 1375\u001b[0m [t \u001b[38;5;28;01mfor\u001b[39;00m t, s, e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_string(instring, maxMatches, debug\u001b[38;5;241m=\u001b[39mdebug)]\n\u001b[1;32m 1376\u001b[0m )\n\u001b[1;32m 1377\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseBaseException \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 1378\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mverbose_stacktrace:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:1262\u001b[0m, in \u001b[0;36mParserElement.scan_string\u001b[0;34m(self, instring, max_matches, overlap, debug, maxMatches)\u001b[0m\n\u001b[1;32m 1260\u001b[0m nextLoc: \u001b[38;5;28mint\u001b[39m\n\u001b[1;32m 1261\u001b[0m tokens: ParseResults\n\u001b[0;32m-> 1262\u001b[0m nextLoc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mparseFn\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpreloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 1263\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseException:\n\u001b[1;32m 1264\u001b[0m loc \u001b[38;5;241m=\u001b[39m preloc \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (8 times), And.parseImpl at line 4080 (3 times), ParseElementEnhance.parseImpl at line 4578 (3 times), Forward.parseImpl at line 5532 (1 times), MatchFirst.parseImpl at line 4309 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5105\u001b[0m, in \u001b[0;36mZeroOrMore.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5103\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m ParseImplReturnType:\n\u001b[1;32m 5104\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 5105\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5106\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ParseException, \u001b[38;5;167;01mIndexError\u001b[39;00m):\n\u001b[1;32m 5107\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, ParseResults([], name\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5004\u001b[0m, in \u001b[0;36m_MultipleMatch.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5002\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m check_ender:\n\u001b[1;32m 5003\u001b[0m try_not_ender(instring, loc)\n\u001b[0;32m-> 5004\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mself_expr_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5005\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 5006\u001b[0m hasIgnoreExprs \u001b[38;5;241m=\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mignoreExprs\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (7 times), ParseElementEnhance.parseImpl at line 4578 (3 times), And.parseImpl at line 4058 (1 times), MatchFirst.parseImpl at line 4309 (1 times), And.parseImpl at line 4080 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5532\u001b[0m, in \u001b[0;36mForward.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5527\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 5528\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mForward expression was never assigned a value, will not parse any input\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 5529\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mstacklevel,\n\u001b[1;32m 5530\u001b[0m )\n\u001b[1;32m 5531\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39m_left_recursion_enabled:\n\u001b[0;32m-> 5532\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5533\u001b[0m \u001b[38;5;66;03m# ## Bounded Recursion algorithm ##\u001b[39;00m\n\u001b[1;32m 5534\u001b[0m \u001b[38;5;66;03m# Recursion only needs to be processed at ``Forward`` elements, since they are\u001b[39;00m\n\u001b[1;32m 5535\u001b[0m \u001b[38;5;66;03m# the only ones that can actually refer to themselves. The general idea is\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 5550\u001b[0m \u001b[38;5;66;03m# we expand using `do_actions=False` and only run `do_actions=True` if the next\u001b[39;00m\n\u001b[1;32m 5551\u001b[0m \u001b[38;5;66;03m# recursion level is acceptable.\u001b[39;00m\n\u001b[1;32m 5552\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mrecursion_lock:\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times), ParseElementEnhance.parseImpl at line 4578 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4080\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4076\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseSyntaxException(\n\u001b[1;32m 4077\u001b[0m instring, \u001b[38;5;28mlen\u001b[39m(instring), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 4078\u001b[0m )\n\u001b[1;32m 4079\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 4080\u001b[0m loc, exprtokens \u001b[38;5;241m=\u001b[39m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4081\u001b[0m resultlist \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m exprtokens\n\u001b[1;32m 4082\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, resultlist\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5239\u001b[0m, in \u001b[0;36mOpt.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5237\u001b[0m self_expr \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexpr\n\u001b[1;32m 5238\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 5239\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[43mself_expr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 5240\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (ParseException, \u001b[38;5;167;01mIndexError\u001b[39;00m):\n\u001b[1;32m 5241\u001b[0m default_value \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdefaultValue\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5532\u001b[0m, in \u001b[0;36mForward.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5527\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 5528\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mForward expression was never assigned a value, will not parse any input\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 5529\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mstacklevel,\n\u001b[1;32m 5530\u001b[0m )\n\u001b[1;32m 5531\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39m_left_recursion_enabled:\n\u001b[0;32m-> 5532\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5533\u001b[0m \u001b[38;5;66;03m# ## Bounded Recursion algorithm ##\u001b[39;00m\n\u001b[1;32m 5534\u001b[0m \u001b[38;5;66;03m# Recursion only needs to be processed at ``Forward`` elements, since they are\u001b[39;00m\n\u001b[1;32m 5535\u001b[0m \u001b[38;5;66;03m# the only ones that can actually refer to themselves. The general idea is\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 5550\u001b[0m \u001b[38;5;66;03m# we expand using `do_actions=False` and only run `do_actions=True` if the next\u001b[39;00m\n\u001b[1;32m 5551\u001b[0m \u001b[38;5;66;03m# recursion level is acceptable.\u001b[39;00m\n\u001b[1;32m 5552\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mrecursion_lock:\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times), ParseElementEnhance.parseImpl at line 4578 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (2 times), ParseElementEnhance.parseImpl at line 4578 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4080\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4076\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseSyntaxException(\n\u001b[1;32m 4077\u001b[0m instring, \u001b[38;5;28mlen\u001b[39m(instring), \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m\n\u001b[1;32m 4078\u001b[0m )\n\u001b[1;32m 4079\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m-> 4080\u001b[0m loc, exprtokens \u001b[38;5;241m=\u001b[39m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4081\u001b[0m resultlist \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m exprtokens\n\u001b[1;32m 4082\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc, resultlist\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:5532\u001b[0m, in \u001b[0;36mForward.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 5527\u001b[0m warnings\u001b[38;5;241m.\u001b[39mwarn(\n\u001b[1;32m 5528\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mForward expression was never assigned a value, will not parse any input\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 5529\u001b[0m stacklevel\u001b[38;5;241m=\u001b[39mstacklevel,\n\u001b[1;32m 5530\u001b[0m )\n\u001b[1;32m 5531\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39m_left_recursion_enabled:\n\u001b[0;32m-> 5532\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 5533\u001b[0m \u001b[38;5;66;03m# ## Bounded Recursion algorithm ##\u001b[39;00m\n\u001b[1;32m 5534\u001b[0m \u001b[38;5;66;03m# Recursion only needs to be processed at ``Forward`` elements, since they are\u001b[39;00m\n\u001b[1;32m 5535\u001b[0m \u001b[38;5;66;03m# the only ones that can actually refer to themselves. The general idea is\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 5550\u001b[0m \u001b[38;5;66;03m# we expand using `do_actions=False` and only run `do_actions=True` if the next\u001b[39;00m\n\u001b[1;32m 5551\u001b[0m \u001b[38;5;66;03m# recursion level is acceptable.\u001b[39;00m\n\u001b[1;32m 5552\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m ParserElement\u001b[38;5;241m.\u001b[39mrecursion_lock:\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (2 times), ParseElementEnhance.parseImpl at line 4578 (2 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (3 times), ParseElementEnhance.parseImpl at line 4578 (2 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - " \u001b[0;31m[... skipping similar frames: ParserElement._parseNoCache at line 851 (1 times)]\u001b[0m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4309\u001b[0m, in \u001b[0;36mMatchFirst.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4307\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs:\n\u001b[1;32m 4308\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4309\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43me\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4310\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseFatalException \u001b[38;5;28;01mas\u001b[39;00m pfe:\n\u001b[1;32m 4311\u001b[0m pfe\u001b[38;5;241m.\u001b[39m__traceback__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:845\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 843\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 844\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m callPreParse \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcallPreparse:\n\u001b[0;32m--> 845\u001b[0m pre_loc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpreParse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 846\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 847\u001b[0m pre_loc \u001b[38;5;241m=\u001b[39m loc\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:793\u001b[0m, in \u001b[0;36mParserElement.preParse\u001b[0;34m(self, instring, loc)\u001b[0m\n\u001b[1;32m 791\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mpreParse\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring: \u001b[38;5;28mstr\u001b[39m, loc: \u001b[38;5;28mint\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m \u001b[38;5;28mint\u001b[39m:\n\u001b[1;32m 792\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mignoreExprs:\n\u001b[0;32m--> 793\u001b[0m loc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_skipIgnorables\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 795\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mskipWhitespace:\n\u001b[1;32m 796\u001b[0m instrlen \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlen\u001b[39m(instring)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:781\u001b[0m, in \u001b[0;36mParserElement._skipIgnorables\u001b[0;34m(self, instring, loc)\u001b[0m\n\u001b[1;32m 779\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 780\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m--> 781\u001b[0m loc, dummy \u001b[38;5;241m=\u001b[39m \u001b[43mignore_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 782\u001b[0m exprsFound \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[1;32m 783\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseException:\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4578\u001b[0m, in \u001b[0;36mParseElementEnhance.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4575\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, loc, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo expression defined\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 4577\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m-> 4578\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexpr\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m)\u001b[49m\n\u001b[1;32m 4579\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ParseSyntaxException:\n\u001b[1;32m 4580\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:851\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmayIndexError \u001b[38;5;129;01mor\u001b[39;00m pre_loc \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m len_instring:\n\u001b[1;32m 850\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 851\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 852\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mIndexError\u001b[39;00m:\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:4058\u001b[0m, in \u001b[0;36mAnd.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 4055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mparseImpl\u001b[39m(\u001b[38;5;28mself\u001b[39m, instring, loc, do_actions\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 4056\u001b[0m \u001b[38;5;66;03m# pass False as callPreParse arg to _parse for first element, since we already\u001b[39;00m\n\u001b[1;32m 4057\u001b[0m \u001b[38;5;66;03m# pre-parsed the string as part of our And pre-parsing\u001b[39;00m\n\u001b[0;32m-> 4058\u001b[0m loc, resultlist \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mexprs\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_parse\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 4059\u001b[0m \u001b[43m \u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallPreParse\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[1;32m 4060\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 4061\u001b[0m errorStop \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m 4062\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m e \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexprs[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[1;32m 4063\u001b[0m \u001b[38;5;66;03m# if isinstance(e, And._ErrorStop):\u001b[39;00m\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:855\u001b[0m, in \u001b[0;36mParserElement._parseNoCache\u001b[0;34m(self, instring, loc, do_actions, callPreParse)\u001b[0m\n\u001b[1;32m 853\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ParseException(instring, len_instring, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrmsg, \u001b[38;5;28mself\u001b[39m)\n\u001b[1;32m 854\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 855\u001b[0m loc, tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mparseImpl\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpre_loc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdo_actions\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 857\u001b[0m tokens \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mpostParse(instring, loc, tokens)\n\u001b[1;32m 859\u001b[0m ret_tokens \u001b[38;5;241m=\u001b[39m ParseResults(\n\u001b[1;32m 860\u001b[0m tokens, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mresultsName, asList\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msaveAsList, modal\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodalResults\n\u001b[1;32m 861\u001b[0m )\n", - "File \u001b[0;32m~/dev/expasy/expasy-chat/.venv/lib/python3.10/site-packages/pyparsing/core.py:2457\u001b[0m, in \u001b[0;36m_SingleCharLiteral.parseImpl\u001b[0;34m(self, instring, loc, do_actions)\u001b[0m\n\u001b[1;32m 2455\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m instring[loc] \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfirstMatchChar:\n\u001b[1;32m 2456\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m loc \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmatch\n\u001b[0;32m-> 2457\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[43mParseException\u001b[49m\u001b[43m(\u001b[49m\u001b[43minstring\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mloc\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43merrmsg\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n", - "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded" - ] - } - ], - "source": [ - "from rdflib.plugins.sparql import prepareQuery\n", - "\n", - "query = \"\"\"\n", - "PREFIX wikibase: \n", - "PREFIX wdt: \n", - "PREFIX bd: \n", - "PREFIX wd: \n", - "PREFIX xsd: \n", - "SELECT ?item ?pic\n", - "WHERE\n", - "{\n", - " SERVICE {\n", - " ?item wdt:P31 wd:Q146 .\n", - "\t\t?item wdt:P18 ?pic\n", - " SERVICE wikibase:label {\n", - " bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\".\n", - " }\n", - " }\n", - "}\n", + "# NOTE: there is a bug in rdflib parser when nested SERVICE https://github.com/RDFLib/rdflib/issues/2136\n", + "# query = \"\"\"\n", + "# PREFIX wikibase: \n", + "# PREFIX wdt: \n", + "# PREFIX bd: \n", + "# PREFIX wd: \n", + "# PREFIX xsd: \n", + "# SELECT ?item ?pic\n", + "# WHERE\n", + "# {\n", + "# SERVICE {\n", + "# ?item wdt:P31 wd:Q146 .\n", + "# \t\t?item wdt:P18 ?pic\n", + "# SERVICE wikibase:label {\n", + "# bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\".\n", + "# }\n", + "# }\n", + "# }\n", "\n", - "\"\"\"\n", - "parsed_query = parseQuery(query)\n", + "# \"\"\"\n", + "# parsed_query = parseQuery(query)\n", "# print(sq.algebra.name)\n", "# print(prepareQuery(toast_query).algebra)\n", "# triples = extract_triples(prepareQuery(sparql_query).algebra)\n", diff --git a/notebooks/rag_embed_queries.ipynb b/notebooks/rag_embed_queries.ipynb index ec4d3a7..4760230 100644 --- a/notebooks/rag_embed_queries.ipynb +++ b/notebooks/rag_embed_queries.ipynb @@ -21,7 +21,7 @@ "metadata": {}, "outputs": [], "source": [ - "from expasy_chat.embed import init_vectordb\n", + "from sparql_llm.embed import init_vectordb\n", "\n", "init_vectordb(\"localhost\")\n", "\n", diff --git a/notebooks/test_example_queries.ipynb b/notebooks/test_example_queries.ipynb index 15332da..93cd8a3 100644 --- a/notebooks/test_example_queries.ipynb +++ b/notebooks/test_example_queries.ipynb @@ -318,8 +318,8 @@ "from rdflib.plugins.sparql import prepareQuery\n", "from SPARQLWrapper import JSON, TURTLE, SPARQLWrapper\n", "\n", - "from expasy_chat.config import settings\n", - "from expasy_chat.embed import get_vectordb\n", + "from sparql_llm.config import settings\n", + "from sparql_llm.embed import get_vectordb\n", "\n", "vectordb = get_vectordb(\"localhost\")\n", "all_queries, _ = vectordb.scroll(\n", diff --git a/notebooks/test_expasy_chat.ipynb b/notebooks/test_expasy_chat.ipynb index 1b2c826..173bbd7 100644 --- a/notebooks/test_expasy_chat.ipynb +++ b/notebooks/test_expasy_chat.ipynb @@ -970,9 +970,9 @@ "import requests\n", "from dotenv import load_dotenv\n", "\n", - "from expasy_chat.config import get_llm_client, settings\n", - "from expasy_chat.utils import query_sparql\n", - "from expasy_chat.validate_sparql import extract_sparql_queries\n", + "from sparql_llm.config import get_llm_client, settings\n", + "from sparql_llm.utils import query_sparql\n", + "from sparql_llm.validate_sparql import extract_sparql_queries\n", "\n", "load_dotenv()\n", "expasy_api_key = os.getenv(\"EXPASY_API_KEY\")\n", diff --git a/notebooks/test_expasy_chat_with_training_data.ipynb b/notebooks/test_expasy_chat_with_training_data.ipynb index 607784c..4562e38 100644 --- a/notebooks/test_expasy_chat_with_training_data.ipynb +++ b/notebooks/test_expasy_chat_with_training_data.ipynb @@ -41,7 +41,7 @@ "from dotenv import load_dotenv\n", "from SPARQLWrapper import JSON, SPARQLWrapper\n", "\n", - "from expasy_chat.utils import extract_sparql_queries\n", + "from sparql_llm.utils import extract_sparql_queries\n", "\n", "load_dotenv()\n", "expasy_api_key = os.getenv(\"EXPASY_API_KEY\")\n", diff --git a/prestart.sh b/prestart.sh index f43e06f..eb55087 100644 --- a/prestart.sh +++ b/prestart.sh @@ -1 +1 @@ -python src/expasy_chat/embed.py +python src/sparql_llm/embed.py diff --git a/pyproject.toml b/pyproject.toml index 9f27312..d222b7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,58 +4,61 @@ build-backend = "hatchling.build" [project] requires-python = ">=3.9" -version = "0.0.1" name = "sparql-llm" -description = "Scripts for Expasy 4, prepare data, train models, etc." +description = "Reusable components and complete webapp to improve Large Language Models (LLMs) capabilities when generating SPARQL queries for a given set of endpoints, using Retrieval-Augmented Generation (RAG) and SPARQL query validation from the endpoint schema." license = "MIT" authors = [ { name = "Vincent Emonet", email = "vincent.emonet@gmail.com" }, ] keywords = [ + "SPARQL", + "LLM", "Expasy", + "KGQA", ] classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", ] +dynamic = ["version"] dependencies = [ "requests", "rdflib", - "oxrdflib", - "pandas", "openai", "bs4", "fastembed", "qdrant_client", "fastapi", - "pydantic >=2.0.0", "langchain", "langchain-core", - "langchain-community", "curies-rs >=0.1.3", + "pydantic >=2.0.0", "pydantic-settings", - # "sparql-llm-utils @ git+https://github.com/vemonet/sparql-llm-utils.git" - # "sparql-llm-utils @ {root:uri}/sparql-llm-utils", ] [project.optional-dependencies] +web = [ + "qdrant_client", + "fastapi", +] test = [ "ruff", + "mypy", + "pyright", "pip-tools", "SPARQLWrapper", "ipykernel", "python-dotenv", "pandas", + # "oxrdflib", + # "langchain-community", # "jupyter", # "ipywidgets", # "tqdm", ] -cpu = [ - "fastembed", -] gpu = [ "onnxruntime-gpu", "fastembed-gpu", @@ -63,11 +66,11 @@ gpu = [ [project.urls] -Homepage = "https://github.com/sib-swiss/expasy-chat" -Documentation = "https://github.com/sib-swiss/expasy-chat" -History = "https://github.com/sib-swiss/expasy-chat/releases" -Tracker = "https://github.com/sib-swiss/expasy-chat/issues" -Source = "https://github.com/sib-swiss/expasy-chat" +Homepage = "https://github.com/sib-swiss/sparql-llm" +Documentation = "https://github.com/sib-swiss/sparql-llm" +History = "https://github.com/sib-swiss/sparql-llm/releases" +Tracker = "https://github.com/sib-swiss/sparql-llm/issues" +Source = "https://github.com/sib-swiss/sparql-llm" # Required for onnxruntime-gpu on CUDA 12 [tool.hatch.envs.default.env-vars] @@ -78,6 +81,7 @@ PIP_EXTRA_INDEX_URL = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_pac [tool.hatch.envs.default] features = [ "test", + "web", ] post-install-commands = [] @@ -86,16 +90,53 @@ fmt = [ "ruff format", "ruff check --fix", ] +test = [ + "fmt", + # "mypy", + # "pyright", + "pytest {args}", +] +cov = [ + "fmt", + "pytest --cov-report html {args}", +] +cov-check = [ + "python -c 'import webbrowser; webbrowser.open(\"http://0.0.0.0:3000\")'", + "python -m http.server 3000 --directory ./htmlcov", +] compile = "pip-compile -o requirements.txt pyproject.toml" ## TOOLS [tool.hatch.build.targets.wheel] -packages = ["src/expasy_chat"] +packages = ["src/sparql_llm"] + +[tool.hatch.version] +path = "src/sparql_llm/__init__.py" # If you need to import packages from git URLs: -[tool.hatch.metadata] -allow-direct-references = true +# [tool.hatch.metadata] +# allow-direct-references = true + + +[tool.mypy] +files = ["src/"] +strict = true +implicit_reexport = true +follow_imports = "normal" +ignore_missing_imports = true +pretty = true +show_column_numbers = true +warn_no_return = true +warn_unused_ignores = true +warn_redundant_casts = true +disallow_untyped_calls = true +disallow_untyped_defs = true +disallow_any_generics = true + +# TODO: Use mypy or pyright for static type checking? +# https://microsoft.github.io/pyright/#/configuration?id=sample-pyprojecttoml-file +# [tool.pyright] # https://github.com/astral-sh/ruff#configuration diff --git a/src/expasy_chat/__init__.py b/src/sparql_llm/__init__.py similarity index 100% rename from src/expasy_chat/__init__.py rename to src/sparql_llm/__init__.py diff --git a/src/expasy_chat/api.py b/src/sparql_llm/api.py similarity index 96% rename from src/expasy_chat/api.py rename to src/sparql_llm/api.py index f82b248..8e4531c 100644 --- a/src/expasy_chat/api.py +++ b/src/sparql_llm/api.py @@ -17,10 +17,10 @@ from rdflib.plugins.sparql.parser import parseQuery from starlette.middleware.cors import CORSMiddleware -from expasy_chat.config import get_llm_client, get_prefixes_dict, settings -from expasy_chat.embed import get_embedding_model, get_vectordb -from expasy_chat.utils import get_prefix_converter -from expasy_chat.validate_sparql import add_missing_prefixes, extract_sparql_queries, validate_sparql_with_void +from sparql_llm.config import get_llm_client, get_prefixes_dict, settings +from sparql_llm.embed import get_embedding_model, get_vectordb +from sparql_llm.utils import get_prefix_converter +from sparql_llm.validate_sparql import add_missing_prefixes, extract_sparql_queries, validate_sparql_with_void # If the user is asking about a named entity warn him that they should check if this entity exist with one of the query used to find named entity # And we provide the this list of queries, and the LLM figure out which query can be used to find the named entity @@ -54,10 +54,10 @@ logging.basicConfig(filename=settings.logs_filepath, level=logging.INFO, format="%(asctime)s - %(message)s") -templates = Jinja2Templates(directory="src/expasy_chat/templates") +templates = Jinja2Templates(directory="src/sparql_llm/templates") app.mount( "/static", - StaticFiles(directory="src/expasy_chat/static"), + StaticFiles(directory="src/sparql_llm/static"), name="static", ) @@ -341,7 +341,7 @@ def chat_ui(request: Request) -> Any: Contact kru@sib.swiss if you have any feedback or suggestions. """, "short_description": "Ask about SIB resources.", - "repository_url": "https://github.com/sib-swiss/expasy-chat", + "repository_url": "https://github.com/sib-swiss/sparql-llm", "examples": [ "Which resources are available at the SIB?", "How can I get the HGNC symbol for the protein P68871?", diff --git a/src/expasy_chat/config.py b/src/sparql_llm/config.py similarity index 97% rename from src/expasy_chat/config.py rename to src/sparql_llm/config.py index 3d2f32b..6a6f783 100644 --- a/src/expasy_chat/config.py +++ b/src/sparql_llm/config.py @@ -1,7 +1,7 @@ from openai import OpenAI from pydantic_settings import BaseSettings, SettingsConfigDict -from expasy_chat.utils import get_prefixes_for_endpoints +from sparql_llm.utils import get_prefixes_for_endpoints # import warnings # warnings.simplefilter(action="ignore", category=UserWarning) @@ -34,7 +34,7 @@ class Settings(BaseSettings): ontology_chunk_size: int = 3000 ontology_chunk_overlap: int = 200 - # Default is the IP address inside the podman network to solve a ridiculous bug from podman + # NOTE: Default is the IP address inside the podman network to solve a ridiculous bug from podman vectordb_host: str = "10.89.0.2" retrieved_queries_count: int = 20 retrieved_docs_count: int = 15 diff --git a/src/expasy_chat/embed.py b/src/sparql_llm/embed.py similarity index 97% rename from src/expasy_chat/embed.py rename to src/sparql_llm/embed.py index 5837b4b..41c9df3 100644 --- a/src/expasy_chat/embed.py +++ b/src/sparql_llm/embed.py @@ -12,10 +12,10 @@ ) from rdflib import RDF, ConjunctiveGraph, Namespace -from expasy_chat.config import settings -from expasy_chat.sparql_examples_loader import SparqlExamplesLoader -from expasy_chat.sparql_void_shapes_loader import SparqlVoidShapesLoader -from expasy_chat.utils import get_prefixes_for_endpoints +from sparql_llm.config import settings +from sparql_llm.sparql_examples_loader import SparqlExamplesLoader +from sparql_llm.sparql_void_shapes_loader import SparqlVoidShapesLoader +from sparql_llm.utils import get_prefixes_for_endpoints def get_embedding_model() -> TextEmbedding: diff --git a/src/expasy_chat/sparql_examples_loader.py b/src/sparql_llm/sparql_examples_loader.py similarity index 80% rename from src/expasy_chat/sparql_examples_loader.py rename to src/sparql_llm/sparql_examples_loader.py index 43cac75..d9c41da 100644 --- a/src/expasy_chat/sparql_examples_loader.py +++ b/src/sparql_llm/sparql_examples_loader.py @@ -7,7 +7,7 @@ from langchain_core.documents import Document from rdflib.plugins.sparql import prepareQuery -from expasy_chat.utils import GET_PREFIXES_QUERY, query_sparql +from sparql_llm.utils import GET_PREFIXES_QUERY, query_sparql GET_SPARQL_EXAMPLES_QUERY = """PREFIX sh: PREFIX rdfs: @@ -41,16 +41,16 @@ def load(self) -> list[Document]: # Get prefixes prefix_map: dict[str, str] = {} - # try: - res = query_sparql(GET_PREFIXES_QUERY, self.endpoint_url) - for row in res["results"]["bindings"]: - # TODO: we might be able to remove this soon, when prefixes will be included in all endpoints - prefix_map[row["prefix"]["value"]] = row["namespace"]["value"] + try: + res = query_sparql(GET_PREFIXES_QUERY, self.endpoint_url) + for row in res["results"]["bindings"]: + # TODO: we might be able to remove this soon, when prefixes will be included in all endpoints + prefix_map[row["prefix"]["value"]] = row["namespace"]["value"] - for row in query_sparql(GET_SPARQL_EXAMPLES_QUERY, self.endpoint_url)["results"]["bindings"]: - docs.append(self._create_document(row, prefix_map)) - # except Exception as e: - # print(f"Could not retrieve SPARQL examples from endpoint {self.endpoint_url}: {e}") + for row in query_sparql(GET_SPARQL_EXAMPLES_QUERY, self.endpoint_url)["results"]["bindings"]: + docs.append(self._create_document(row, prefix_map)) + except Exception as e: + print(f"Could not retrieve SPARQL query examples from endpoint {self.endpoint_url}: {e}") if self.verbose: print(f"Found {len(docs)} examples queries for {self.endpoint_url}") diff --git a/src/expasy_chat/sparql_void_shapes_loader.py b/src/sparql_llm/sparql_void_shapes_loader.py similarity index 97% rename from src/expasy_chat/sparql_void_shapes_loader.py rename to src/sparql_llm/sparql_void_shapes_loader.py index 331363d..ee4d2b0 100644 --- a/src/expasy_chat/sparql_void_shapes_loader.py +++ b/src/sparql_llm/sparql_void_shapes_loader.py @@ -1,7 +1,7 @@ from langchain_core.document_loaders.base import BaseLoader from langchain_core.documents import Document -from expasy_chat.void_to_shex import get_shex_dict_from_void +from sparql_llm.void_to_shex import get_shex_dict_from_void class SparqlVoidShapesLoader(BaseLoader): diff --git a/src/expasy_chat/static/sparql.js b/src/sparql_llm/static/sparql.js similarity index 100% rename from src/expasy_chat/static/sparql.js rename to src/sparql_llm/static/sparql.js diff --git a/src/expasy_chat/static/turtle.js b/src/sparql_llm/static/turtle.js similarity index 100% rename from src/expasy_chat/static/turtle.js rename to src/sparql_llm/static/turtle.js diff --git a/src/expasy_chat/templates/index.html b/src/sparql_llm/templates/index.html similarity index 100% rename from src/expasy_chat/templates/index.html rename to src/sparql_llm/templates/index.html diff --git a/src/expasy_chat/utils.py b/src/sparql_llm/utils.py similarity index 100% rename from src/expasy_chat/utils.py rename to src/sparql_llm/utils.py diff --git a/src/expasy_chat/validate_sparql.py b/src/sparql_llm/validate_sparql.py similarity index 99% rename from src/expasy_chat/validate_sparql.py rename to src/sparql_llm/validate_sparql.py index 3d18a8d..19a6e10 100644 --- a/src/expasy_chat/validate_sparql.py +++ b/src/sparql_llm/validate_sparql.py @@ -7,7 +7,7 @@ from rdflib.paths import AlternativePath, MulPath, Path, SequencePath from rdflib.plugins.sparql import prepareQuery -from expasy_chat.utils import TripleDict, get_prefix_converter, get_prefixes_for_endpoints, get_void_dict +from sparql_llm.utils import TripleDict, get_prefix_converter, get_prefixes_for_endpoints, get_void_dict queries_pattern = re.compile(r"```sparql(.*?)```", re.DOTALL) endpoint_pattern = re.compile(r"^#.*(https?://[^\s]+)", re.MULTILINE) diff --git a/src/expasy_chat/void_to_shex.py b/src/sparql_llm/void_to_shex.py similarity index 97% rename from src/expasy_chat/void_to_shex.py rename to src/sparql_llm/void_to_shex.py index 6214428..d72d242 100644 --- a/src/expasy_chat/void_to_shex.py +++ b/src/sparql_llm/void_to_shex.py @@ -1,4 +1,4 @@ -from expasy_chat.utils import get_prefix_converter, get_prefixes_for_endpoints, get_void_dict, query_sparql +from sparql_llm.utils import get_prefix_converter, get_prefixes_for_endpoints, get_void_dict, query_sparql DEFAULT_NAMESPACES_TO_IGNORE = [ "http://www.w3.org/ns/sparql-service-description#", diff --git a/tests/test_sparql_llm.py b/tests/test_components.py similarity index 87% rename from tests/test_sparql_llm.py rename to tests/test_components.py index 89df724..3b115f6 100644 --- a/tests/test_sparql_llm.py +++ b/tests/test_components.py @@ -1,4 +1,4 @@ -from expasy_chat import ( +from sparql_llm import ( SparqlExamplesLoader, SparqlVoidShapesLoader, __version__, @@ -14,14 +14,14 @@ def test_sparql_examples_loader_uniprot(): assert len(docs) >= 10 -def test_sparql_examples_loader_error_nextprot(): - """Test the SPARQL queries examples loader with the UniProt endpoint.""" - try: - loader = SparqlExamplesLoader("https://sparql.nextprot.orgg/") - _docs = loader.load() - raise AssertionError("Should have raised an error") - except Exception as e: - assert "Failed to resolve" in str(e) +# def test_sparql_examples_loader_error_nextprot(): +# """Test the SPARQL queries examples loader with the UniProt endpoint.""" +# try: +# loader = SparqlExamplesLoader("https://sparql.nextprot.orgg/") +# _docs = loader.load() +# raise AssertionError("Should have raised an error") +# except Exception as e: +# assert "Failed to resolve" in str(e) def test_sparql_void_shape_loader(): @@ -80,12 +80,10 @@ def test_validate_sparql_with_void(): ?anatEntity rdfs:label 'brain' . ?ratOrganism obo:RO_0002162 taxon:10116 . } -} -""" - +}""" issues = validate_sparql_with_void(sparql_query, "https://sparql.uniprot.org/sparql/") # print("\n".join(issues)) - assert len(issues) >= 2 + assert len(issues) == 4 def test_version():