Skip to content

Commit 6284cdd

Browse files
authored
Remove fnllm (#2095)
1 parent eb0dfe3 commit 6284cdd

File tree

25 files changed

+122
-1163
lines changed

25 files changed

+122
-1163
lines changed

docs/config/env_vars.md

Lines changed: 0 additions & 219 deletions
This file was deleted.

docs/config/models.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ This page contains information on selecting a model to use and options to supply
66

77
GraphRAG was built and tested using OpenAI models, so this is the default model set we support. This is not intended to be a limiter or statement of quality or fitness for your use case, only that it's the set we are most familiar with for prompting, tuning, and debugging.
88

9-
GraphRAG also utilizes a language model wrapper library used by several projects within our team, called fnllm. fnllm provides two important functions for GraphRAG: rate limiting configuration to help us maximize throughput for large indexing jobs, and robust caching of API calls to minimize consumption on repeated indexes for testing, experimentation, or incremental ingest. fnllm uses the OpenAI Python SDK under the covers, so OpenAI-compliant endpoints are a base requirement out-of-the-box.
10-
11-
Starting with version 2.6.0, GraphRAG supports using [LiteLLM](https://docs.litellm.ai/) instead of fnllm for calling language models. LiteLLM provides support for 100+ models though it is important to note that when choosing a model it must support returning [structured outputs](https://openai.com/index/introducing-structured-outputs-in-the-api/) adhering to a [JSON schema](https://docs.litellm.ai/docs/completion/json_mode).
9+
Starting with version 2.6.0, GraphRAG supports using [LiteLLM](https://docs.litellm.ai/) for calling language models. LiteLLM provides support for 100+ models though it is important to note that when choosing a model it must support returning [structured outputs](https://openai.com/index/introducing-structured-outputs-in-the-api/) adhering to a [JSON schema](https://docs.litellm.ai/docs/completion/json_mode).
1210

1311
Example using LiteLLm as the language model tool for GraphRAG:
1412

@@ -54,13 +52,15 @@ Example config with asymmetric model use:
5452
models:
5553
extraction_chat_model:
5654
api_key: ${GRAPHRAG_API_KEY}
57-
type: openai_chat
55+
type: chat
56+
model_provider: openai
5857
auth_type: api_key
5958
model: gpt-4o
6059
model_supports_json: true
6160
query_chat_model:
6261
api_key: ${GRAPHRAG_API_KEY}
63-
type: openai_chat
62+
type: chat
63+
model_provider: openai
6464
auth_type: api_key
6565
model: o1
6666
model_supports_json: true
@@ -98,7 +98,6 @@ Many users have used platforms such as [ollama](https://ollama.com/) and [LiteLL
9898
As of GraphRAG 2.0.0, we support model injection through the use of a standard chat and embedding Protocol and an accompanying ModelFactory that you can use to register your model implementation. This is not supported with the CLI, so you'll need to use GraphRAG as a library.
9999

100100
- Our Protocol is [defined here](https://github.com/microsoft/graphrag/blob/main/graphrag/language_model/protocol/base.py)
101-
- Our base implementation, which wraps fnllm, [is here](https://github.com/microsoft/graphrag/blob/main/graphrag/language_model/providers/fnllm/models.py)
102101
- We have a simple mock implementation in our tests that you can [reference here](https://github.com/microsoft/graphrag/blob/main/tests/mock_provider.py)
103102

104103
Once you have a model implementation, you need to register it with our ModelFactory:

docs/config/yaml.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,22 @@ For example:
2828
models:
2929
default_chat_model:
3030
api_key: ${GRAPHRAG_API_KEY}
31-
type: openai_chat
31+
type: chat
32+
model_provider: openai
3233
model: gpt-4.1
3334
model_supports_json: true
3435
default_embedding_model:
3536
api_key: ${GRAPHRAG_API_KEY}
36-
type: openai_embedding
37+
type: embedding
38+
model_provider: openai
3739
model: text-embedding-3-large
3840
```
3941
4042
#### Fields
4143
4244
- `api_key` **str** - The OpenAI API key to use.
4345
- `auth_type` **api_key|azure_managed_identity** - Indicate how you want to authenticate requests.
44-
- `type` **chat**|**embedding**|**openai_chat|azure_openai_chat|openai_embedding|azure_openai_embedding|mock_chat|mock_embeddings** - The type of LLM to use.
46+
- `type` **chat**|**embedding**|mock_chat|mock_embeddings** - The type of LLM to use.
4547
- `model_provider` **str|None** - The model provider to use, e.g., openai, azure, anthropic, etc. Required when `type == chat|embedding`. When `type == chat|embedding`, [LiteLLM](https://docs.litellm.ai/) is used under the hood which has support for calling 100+ models. [View LiteLLm basic usage](https://docs.litellm.ai/docs/#basic-usage) for details on how models are called (The `model_provider` is the portion prior to `/` while the `model` is the portion following the `/`). [View Language Model Selection](models.md) for more details and examples on using LiteLLM.
4648
- `model` **str** - The model name.
4749
- `encoding_model` **str** - The text encoding model to use. Default is to use the encoding model aligned with the language model (i.e., it is retrieved from tiktoken if unset).

docs/examples_notebooks/custom_vector_store.ipynb

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -318,27 +318,19 @@
318318
"sample_documents = [\n",
319319
" VectorStoreDocument(\n",
320320
" id=\"doc_1\",\n",
321-
" text=\"GraphRAG is a powerful knowledge graph extraction and reasoning framework.\",\n",
322321
" vector=create_mock_embedding(),\n",
323-
" attributes={\"category\": \"technology\", \"source\": \"documentation\"},\n",
324322
" ),\n",
325323
" VectorStoreDocument(\n",
326324
" id=\"doc_2\",\n",
327-
" text=\"Vector stores enable efficient similarity search over high-dimensional data.\",\n",
328325
" vector=create_mock_embedding(),\n",
329-
" attributes={\"category\": \"technology\", \"source\": \"research\"},\n",
330326
" ),\n",
331327
" VectorStoreDocument(\n",
332328
" id=\"doc_3\",\n",
333-
" text=\"Machine learning models can process and understand natural language text.\",\n",
334329
" vector=create_mock_embedding(),\n",
335-
" attributes={\"category\": \"AI\", \"source\": \"article\"},\n",
336330
" ),\n",
337331
" VectorStoreDocument(\n",
338332
" id=\"doc_4\",\n",
339-
" text=\"Custom implementations allow for specialized behavior and integration.\",\n",
340333
" vector=create_mock_embedding(),\n",
341-
" attributes={\"category\": \"development\", \"source\": \"tutorial\"},\n",
342334
" ),\n",
343335
"]\n",
344336
"\n",
@@ -395,9 +387,7 @@
395387
"for i, result in enumerate(search_results, 1):\n",
396388
" doc = result.document\n",
397389
" print(f\"{i}. ID: {doc.id}\")\n",
398-
" print(f\" Text: {doc.text[:60]}...\")\n",
399390
" print(f\" Similarity Score: {result.score:.4f}\")\n",
400-
" print(f\" Category: {doc.attributes.get('category', 'N/A')}\")\n",
401391
" print()"
402392
]
403393
},
@@ -412,14 +402,8 @@
412402
" found_doc = vector_store.search_by_id(\"doc_2\")\n",
413403
" print(\"✅ Found document by ID:\")\n",
414404
" print(f\" ID: {found_doc.id}\")\n",
415-
" print(f\" Text: {found_doc.text}\")\n",
416-
" print(f\" Attributes: {found_doc.attributes}\")\n",
417405
"except KeyError as e:\n",
418-
" print(f\"❌ Error: {e}\")\n",
419-
"\n",
420-
"# Test filter by ID\n",
421-
"id_filter = vector_store.filter_by_id([\"doc_1\", \"doc_3\"])\n",
422-
"print(f\"\\n🔧 ID filter result: {id_filter}\")"
406+
" print(f\"❌ Error: {e}\")"
423407
]
424408
},
425409
{
@@ -450,7 +434,8 @@
450434
" # Other GraphRAG configuration...\n",
451435
" \"models\": {\n",
452436
" \"default_embedding_model\": {\n",
453-
" \"type\": \"openai_embedding\",\n",
437+
" \"type\": \"embedding\",\n",
438+
" \"model_provider\": \"openai\",\n",
454439
" \"model\": \"text-embedding-3-small\",\n",
455440
" }\n",
456441
" },\n",
@@ -500,9 +485,7 @@
500485
" entity_documents = [\n",
501486
" VectorStoreDocument(\n",
502487
" id=f\"entity_{i}\",\n",
503-
" text=f\"Entity {i} description: Important concept in the knowledge graph\",\n",
504488
" vector=create_mock_embedding(),\n",
505-
" attributes={\"type\": \"entity\", \"importance\": i % 3 + 1},\n",
506489
" )\n",
507490
" for i in range(10)\n",
508491
" ]\n",

docs/get_started.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ If running in OpenAI mode, you only need to update the value of `GRAPHRAG_API_KE
6060
In addition to setting your API key, Azure OpenAI users should set the variables below in the settings.yaml file. To find the appropriate sections, just search for the `models:` root configuration; you should see two sections, one for the default chat endpoint and one for the default embeddings endpoint. Here is an example of what to add to the chat model config:
6161

6262
```yaml
63-
type: azure_openai_chat # Or azure_openai_embedding for embeddings
63+
type: chat
64+
model_provider: azure
6465
api_base: https://<instance>.openai.azure.com
6566
api_version: 2024-02-15-preview # You can customize this for other versions
66-
deployment_name: <azure_model_deployment_name>
6767
```
6868
69+
Most people tend to name their deployments the same as their model - if yours are different, add the `deployment_name` as well.
70+
6971
#### Using Managed Auth on Azure
7072
To use managed auth, edit the auth_type in your model config and *remove* the api_key line:
7173

graphrag/config/enums.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,9 @@ class ModelType(str, Enum):
8484
"""LLMType enum class definition."""
8585

8686
# Embeddings
87-
OpenAIEmbedding = "openai_embedding"
88-
AzureOpenAIEmbedding = "azure_openai_embedding"
8987
Embedding = "embedding"
9088

9189
# Chat Completion
92-
OpenAIChat = "openai_chat"
93-
AzureOpenAIChat = "azure_openai_chat"
9490
Chat = "chat"
9591

9692
# Debug

graphrag/config/models/language_model_config.py

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
"""Language model configuration."""
55

66
import logging
7-
from typing import Literal
87

9-
import tiktoken
108
from pydantic import BaseModel, Field, model_validator
119

1210
from graphrag.config.defaults import language_model_defaults
@@ -77,9 +75,7 @@ def _validate_auth_type(self) -> None:
7775
"""
7876
if (
7977
self.auth_type == AuthType.AzureManagedIdentity
80-
and self.type != ModelType.AzureOpenAIChat
81-
and self.type != ModelType.AzureOpenAIEmbedding
82-
and self.model_provider != "azure" # indicates Litellm + AOI
78+
and self.model_provider != "azure"
8379
):
8480
msg = f"auth_type of azure_managed_identity is not supported for model type {self.type}. Please rerun `graphrag init` and set the auth_type to api_key."
8581
raise ConflictingSettingsError(msg)
@@ -98,14 +94,6 @@ def _validate_type(self) -> None:
9894
if not ModelFactory.is_supported_model(self.type):
9995
msg = f"Model type {self.type} is not recognized, must be one of {ModelFactory.get_chat_models() + ModelFactory.get_embedding_models()}."
10096
raise KeyError(msg)
101-
if self.type in [
102-
"openai_chat",
103-
"openai_embedding",
104-
"azure_openai_chat",
105-
"azure_openai_embedding",
106-
]:
107-
msg = f"Model config based on fnllm is deprecated and will be removed in GraphRAG v3, please use {ModelType.Chat} or {ModelType.Embedding} instead to switch to LiteLLM config."
108-
logger.warning(msg)
10997

11098
model_provider: str | None = Field(
11199
description="The model provider to use.",
@@ -134,32 +122,6 @@ def _validate_model_provider(self) -> None:
134122
default=language_model_defaults.encoding_model,
135123
)
136124

137-
def _validate_encoding_model(self) -> None:
138-
"""Validate the encoding model.
139-
140-
The default behavior is to use an encoding model that matches the LLM model.
141-
LiteLLM supports 100+ models and their tokenization. There is no need to
142-
set the encoding model when using the new LiteLLM provider as was done with fnllm provider.
143-
144-
Users can still manually specify a tiktoken based encoding model to use even with the LiteLLM provider
145-
in which case the specified encoding model will be used regardless of the LLM model being used, even if
146-
it is not an openai based model.
147-
148-
If not using LiteLLM provider, set the encoding model based on the LLM model name.
149-
This is for backward compatibility with existing fnllm provider until fnllm is removed.
150-
151-
Raises
152-
------
153-
KeyError
154-
If the model name is not recognized.
155-
"""
156-
if (
157-
self.type != ModelType.Chat
158-
and self.type != ModelType.Embedding
159-
and self.encoding_model.strip() == ""
160-
):
161-
self.encoding_model = tiktoken.encoding_name_for_model(self.model)
162-
163125
api_base: str | None = Field(
164126
description="The base URL for the LLM API.",
165127
default=language_model_defaults.api_base,
@@ -175,11 +137,9 @@ def _validate_api_base(self) -> None:
175137
AzureApiBaseMissingError
176138
If the API base is missing and is required.
177139
"""
178-
if (
179-
self.type == ModelType.AzureOpenAIChat
180-
or self.type == ModelType.AzureOpenAIEmbedding
181-
or self.model_provider == "azure" # indicates Litellm + AOI
182-
) and (self.api_base is None or self.api_base.strip() == ""):
140+
if (self.model_provider == "azure") and (
141+
self.api_base is None or self.api_base.strip() == ""
142+
):
183143
raise AzureApiBaseMissingError(self.type)
184144

185145
api_version: str | None = Field(
@@ -197,11 +157,9 @@ def _validate_api_version(self) -> None:
197157
AzureApiBaseMissingError
198158
If the API base is missing and is required.
199159
"""
200-
if (
201-
self.type == ModelType.AzureOpenAIChat
202-
or self.type == ModelType.AzureOpenAIEmbedding
203-
or self.model_provider == "azure" # indicates Litellm + AOI
204-
) and (self.api_version is None or self.api_version.strip() == ""):
160+
if (self.model_provider == "azure") and (
161+
self.api_version is None or self.api_version.strip() == ""
162+
):
205163
raise AzureApiVersionMissingError(self.type)
206164

207165
deployment_name: str | None = Field(
@@ -219,11 +177,9 @@ def _validate_deployment_name(self) -> None:
219177
AzureDeploymentNameMissingError
220178
If the deployment name is missing and is required.
221179
"""
222-
if (
223-
self.type == ModelType.AzureOpenAIChat
224-
or self.type == ModelType.AzureOpenAIEmbedding
225-
or self.model_provider == "azure" # indicates Litellm + AOI
226-
) and (self.deployment_name is None or self.deployment_name.strip() == ""):
180+
if (self.model_provider == "azure") and (
181+
self.deployment_name is None or self.deployment_name.strip() == ""
182+
):
227183
msg = f"deployment_name is not set for Azure-hosted model. This will default to your model name ({self.model}). If different, this should be set."
228184
logger.debug(msg)
229185

@@ -247,7 +203,7 @@ def _validate_deployment_name(self) -> None:
247203
description="The request timeout to use.",
248204
default=language_model_defaults.request_timeout,
249205
)
250-
tokens_per_minute: int | Literal["auto"] | None = Field(
206+
tokens_per_minute: int | None = Field(
251207
description="The number of tokens per minute to use for the LLM service.",
252208
default=language_model_defaults.tokens_per_minute,
253209
)
@@ -262,18 +218,10 @@ def _validate_tokens_per_minute(self) -> None:
262218
"""
263219
# If the value is a number, check if it is less than 1
264220
if isinstance(self.tokens_per_minute, int) and self.tokens_per_minute < 1:
265-
msg = f"Tokens per minute must be a non zero positive number, 'auto' or null. Suggested value: {language_model_defaults.tokens_per_minute}."
221+
msg = f"Tokens per minute must be a non zero positive number or null. Suggested value: {language_model_defaults.tokens_per_minute}."
266222
raise ValueError(msg)
267223

268-
if (
269-
(self.type == ModelType.Chat or self.type == ModelType.Embedding)
270-
and self.rate_limit_strategy is not None
271-
and self.tokens_per_minute == "auto"
272-
):
273-
msg = f"tokens_per_minute cannot be set to 'auto' when using type '{self.type}'. Please set it to a positive integer or null to disable."
274-
raise ValueError(msg)
275-
276-
requests_per_minute: int | Literal["auto"] | None = Field(
224+
requests_per_minute: int | None = Field(
277225
description="The number of requests per minute to use for the LLM service.",
278226
default=language_model_defaults.requests_per_minute,
279227
)
@@ -288,15 +236,7 @@ def _validate_requests_per_minute(self) -> None:
288236
"""
289237
# If the value is a number, check if it is less than 1
290238
if isinstance(self.requests_per_minute, int) and self.requests_per_minute < 1:
291-
msg = f"Requests per minute must be a non zero positive number, 'auto' or null. Suggested value: {language_model_defaults.requests_per_minute}."
292-
raise ValueError(msg)
293-
294-
if (
295-
(self.type == ModelType.Chat or self.type == ModelType.Embedding)
296-
and self.rate_limit_strategy is not None
297-
and self.requests_per_minute == "auto"
298-
):
299-
msg = f"requests_per_minute cannot be set to 'auto' when using type '{self.type}'. Please set it to a positive integer or null to disable."
239+
msg = f"Requests per minute must be a non zero positive number or null. Suggested value: {language_model_defaults.requests_per_minute}."
300240
raise ValueError(msg)
301241

302242
rate_limit_strategy: str | None = Field(
@@ -399,5 +339,4 @@ def _validate_model(self):
399339
self._validate_requests_per_minute()
400340
self._validate_max_retries()
401341
self._validate_azure_settings()
402-
self._validate_encoding_model()
403342
return self

graphrag/index/operations/chunk_text/strategies.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from collections.abc import Iterable
77

88
import nltk
9-
import tiktoken
109

1110
from graphrag.config.models.chunking_config import ChunkingConfig
1211
from graphrag.index.operations.chunk_text.typing import TextChunk
@@ -15,21 +14,7 @@
1514
split_multiple_texts_on_tokens,
1615
)
1716
from graphrag.logger.progress import ProgressTicker
18-
19-
20-
def get_encoding_fn(encoding_name):
21-
"""Get the encoding model."""
22-
enc = tiktoken.get_encoding(encoding_name)
23-
24-
def encode(text: str) -> list[int]:
25-
if not isinstance(text, str):
26-
text = f"{text}"
27-
return enc.encode(text)
28-
29-
def decode(tokens: list[int]) -> str:
30-
return enc.decode(tokens)
31-
32-
return encode, decode
17+
from graphrag.tokenizer.get_tokenizer import get_tokenizer
3318

3419

3520
def run_tokens(
@@ -40,16 +25,14 @@ def run_tokens(
4025
"""Chunks text into chunks based on encoding tokens."""
4126
tokens_per_chunk = config.size
4227
chunk_overlap = config.overlap
43-
encoding_name = config.encoding_model
44-
45-
encode, decode = get_encoding_fn(encoding_name)
28+
tokenizer = get_tokenizer(encoding_model=config.encoding_model)
4629
return split_multiple_texts_on_tokens(
4730
input,
4831
TokenChunkerOptions(
4932
chunk_overlap=chunk_overlap,
5033
tokens_per_chunk=tokens_per_chunk,
51-
encode=encode,
52-
decode=decode,
34+
encode=tokenizer.encode,
35+
decode=tokenizer.decode,
5336
),
5437
tick,
5538
)

0 commit comments

Comments
 (0)