circlemind-ai / fast-graphrag

RAG that intelligently adapts to your use case, data, and queries
MIT License
1.98k stars 84 forks source link

Instructor Library Error: Multiple Tool Calls Not Supported When Using Ollama LLM Service #20

Open boyanxu opened 3 days ago

boyanxu commented 3 days ago

Describe the bug The Instructor library is throwing an error when attempting to process multiple tool calls. The specific error is "Instructor does not support multiple tool calls, use List[Model] instead".

To Reproduce

  1. Install fast-graphrag with poetry.
  2. Following example instruction to download book.txt
from fast_graphrag import GraphRAG
from fast_graphrag._llm import OllamaAIEmbeddingService, OllamaAILLMService

DOMAIN = "Analyze this story and identify the characters. Focus on how they interact with each other, the locations they explore, and their relationships."

EXAMPLE_QUERIES = [
    "What is the significance of Christmas Eve in A Christmas Carol?",
    "How does the setting of Victorian London contribute to the story's themes?",
    "Describe the chain of events that leads to Scrooge's transformation.",
    "How does Dickens use the different spirits (Past, Present, and Future) to guide Scrooge?",
    "Why does Dickens choose to divide the story into \"staves\" rather than chapters?"
]

ENTITY_TYPES = ["Character", "Animal", "Place", "Object", "Activity", "Event"]

api_key = "ollama"
grag = GraphRAG(
    working_dir="./book_example",
    n_checkpoints=2,
    domain=DOMAIN,
    example_queries="\n".join(EXAMPLE_QUERIES),
    entity_types=ENTITY_TYPES,
    config=GraphRAG.Config(
        llm_service=OllamaAILLMService(
            model="llama3.2",
            base_url="http://localhost:11434/v1",
            api_key=api_key,
        ),
        embedding_service=OllamaAIEmbeddingService(
            model="nomic-embed-text:v1.5",
            base_url="http://localhost:11434/api/embeddings/",
            api_key=api_key,
            embedding_dim=512,
        ),
    ),
)

with open("./book.txt") as f:
    grag.insert(f.read())

print(grag.query("Who is Scrooge?").response)

Error

Extracting subgraphs:   0%|                                                                                                                                                 | 0/8 [00:00<?, ?it/sE
rror during information extraction from document: Instructor does not support multiple tool calls, use List[Model] instead                                                  | 0/1 [00:00<?, ?it/s]Extracting subgraphs:   0%|                                                                                                                                                 | 0/8 [00:40<?, ?it/s]
Error during query: Instructor does not support multiple tool calls, use List[Model] instead                                                                                                      
Traceback (most recent call last):
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/retry.py", line 228, in retry_async
    return await process_response_async(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/process_response.py", line 82, in process_response_async
    model = response_model.from_response(
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/function_calls.py", line 153, in from_response
    return cls.parse_tools(completion, validation_context, strict)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/function_calls.py", line 338, in parse_tools
    len(message.tool_calls or []) == 1
AssertionError: Instructor does not support multiple tool calls, use List[Model] instead

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/retry.py", line 217, in retry_async
    async for attempt in max_retries:
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 166, in __anext__
    do = await self.iter(retry_state=self._retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 153, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/_utils.py", line 99, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/__init__.py", line 419, in exc_check
    raise retry_exc from fut.exception()
tenacity.RetryError: RetryError[<Future at 0x104ed9100 state=finished raised AssertionError>]

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/boyanxu/Downloads/fast-graphrag/examples/llama.py", line 33, in <module>
    print(grag.query("Who is Campbell?").response)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_graphrag.py", line 138, in query
    return get_event_loop().run_until_complete(_query())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/.pyenv/versions/3.12.7/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_graphrag.py", line 134, in _query
    raise e
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_graphrag.py", line 130, in _query
    answer = await self.async_query(query, params)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_graphrag.py", line 156, in async_query
    extracted_entities = await self.information_extraction_service.extract_entities_from_query(
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_services/_information_extraction.py", line 39, in extract_entities_from_query
    entities, _ = await format_and_send_prompt(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_llm/_base.py", line 40, in format_and_send_prompt
    return await llm.send_message(prompt=formatted_prompt, response_model=response_model, **args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 189, in async_wrapped
    return await copy(fn, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 111, in __call__
    do = await self.iter(retry_state=retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 153, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/_utils.py", line 99, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/__init__.py", line 398, in <lambda>
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/.pyenv/versions/3.12.7/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/.pyenv/versions/3.12.7/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 114, in __call__
    result = await fn(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Downloads/fast-graphrag/fast_graphrag/_llm/_llm_openai.py", line 79, in send_message
    llm_response: GTResponseModel = await self.llm_async_client.chat.completions.create(
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/client.py", line 387, in create
    return await self.create_fn(
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/patch.py", line 161, in new_create_async
    response = await retry_async(
               ^^^^^^^^^^^^^^^^^^
  File "/Users/boyanxu/Library/Caches/pypoetry/virtualenvs/fast-graphrag-YhZRNWrv-py3.12/lib/python3.12/site-packages/instructor/retry.py", line 248, in retry_async
    raise InstructorRetryException(
instructor.exceptions.InstructorRetryException: Instructor does not support multiple tool calls, use List[Model] instead
liukidar commented 3 days ago

To be honest I am not sure what's happening here. It could be something due to using async clients. Anyway, we are planning to switch from TOOL mode to JSON schema mode which hopefully should fix the issue (see here for example: https://github.com/instructor-ai/instructor/issues/840)

boyanxu commented 2 days ago

Thanks for the kind reply. Anyway, impressive work. I'll keep an eye on the updates.

mayt commented 2 days ago

Probably a bug with this line. (The OllamaAILLMService was recently removed though)

https://github.com/circlemind-ai/fast-graphrag/commit/cdcfeeb05f030064dda7db1ed1bb537956ec0856#diff-61500fd6e265e64a6928ff34e8c1cb5dcedf550abf339f7c82d4af7601662127L24

maybe setting the Mode to just JSON might fix it.

self.llm_async_client = instructor.from_openai(
    ollama_client,
    mode=instructor.Mode.JSON
)
liukidar commented 2 days ago

Yes, I removed that so that we don't have to support two things (the openai interface is the same, just different base_url). Indeed we will try the JSON schema thing, we just need to transform all the answers in json (a couple are plain strings as of know). I should be able to do that tomorrow or the day after :)

9prodhi commented 14 hours ago

The custom LLM implementation is running without errors, but it fails to produce any output. During the extraction process, the following validation errors occur:

Error during information extraction from document: 3 validation errors for Model
entities
  Field required [type=missing, input_value={'Model': {'entities': [{...y7 and OtherEntity8'}]}}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing
relationships
  Field required [type=missing, input_value={'Model': {'entities': [{...y7 and OtherEntity8'}]}}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing
other_relationships
  Field required [type=missing, input_value={'Model': {'entities': [{...y7 and OtherEntity8'}]}}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing

Steps to Reproduce

Custom LLM Exampl

"""Example usage of GraphRAG with custom LLM and Embedding services compatible with the OpenAI API."""
from typing import List
from dotenv import load_dotenv
from fast_graphrag import GraphRAG
from fast_graphrag._llm import OpenAIEmbeddingService, OpenAILLMService

load_dotenv()

DOMAIN = "Analyze this topic and identify the core concepts. Focus on how they interact with each other, the topics they explore, and their relationships."
QUERIES: List[str] = [
    "What is transformer?",
    "How transformers work?"
]
ENTITY_TYPES: List[str] = ["Topic", "Concept"]

working_dir = "./examples/transformer"

grag = GraphRAG(
    working_dir=working_dir,
    domain=DOMAIN,
    example_queries="\n".join(QUERIES),
    entity_types=ENTITY_TYPES,
    config=GraphRAG.Config(
        llm_service=OpenAILLMService(model="mistral", base_url="http://localhost:11434/v1", api_key="your-api-key"),
        embedding_service=OpenAIEmbeddingService(
            model="nomic-ai/nomic-embed-text-v1.5-GGUF",
            base_url="http://localhost:8001/v1",
            api_key="your-api-key",
            embedding_dim=768,  # the output embedding dim of the chosen model
        ),
    ),
)

with open("./examples/transformer/Transformers_intro.txt") as f:
    grag.insert(f.read())

print(grag.query("Who is transformer?").response)

Modify fast_graphrag/_llm/_llm_openai.py

def __post_init__(self):
    logger.debug("Initialized OpenAILLMService with patched OpenAI client.")
    self.llm_async_client: instructor.AsyncInstructor = instructor.from_openai(
        AsyncOpenAI(base_url=self.base_url, api_key=self.api_key, timeout=TIMEOUT_SECONDS),
        mode=instructor.Mode.JSON,
    )

Ref : https://python.useinstructor.com/examples/ollama/?h=ollama#ollama

The OpenAI Python client doesn't work properly with the embedding API of Ollama. I created a small utility to work around this limitation. You can find the utility here.

Any guidance on how to handle the LLM model input to resolve the issue would be greatly appreciated.