Merge branch 'getzep:main' into main

This commit is contained in:
MirzaBicer 2025-07-07 16:18:43 +03:00 committed by GitHub
commit fe73e93464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 2218 additions and 1986 deletions

View file

@ -116,7 +116,7 @@ Once you've found an issue tagged with "good first issue" or "help wanted," or p
We use several tools to maintain code quality:
- Ruff for linting and formatting
- Mypy for static type checking
- Pyright for static type checking
- Pytest for testing
Before submitting a pull request, please run:
@ -127,6 +127,67 @@ make check
This command will format your code, run linting checks, and execute tests.
## Third-Party Integrations
When contributing integrations for third-party services (LLM providers, embedding services, databases, etc.), please follow these patterns:
### Optional Dependencies
All third-party integrations must be optional dependencies to keep the core library lightweight. Follow this pattern:
1. **Add to `pyproject.toml`**: Define your dependency as an optional extra AND include it in the dev extra:
```toml
[project.optional-dependencies]
your-service = ["your-package>=1.0.0"]
dev = [
# ... existing dev dependencies
"your-package>=1.0.0", # Include all optional extras here
# ... other dependencies
]
```
2. **Use TYPE_CHECKING pattern**: In your integration module, import dependencies conditionally:
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import your_package
from your_package import SomeType
else:
try:
import your_package
from your_package import SomeType
except ImportError:
raise ImportError(
'your-package is required for YourServiceClient. '
'Install it with: pip install graphiti-core[your-service]'
) from None
```
3. **Benefits of this pattern**:
- Fast startup times (no import overhead during type checking)
- Clear error messages with installation instructions
- Proper type hints for development
- Consistent user experience
4. **Do NOT**:
- Add optional imports to `__init__.py` files
- Use direct imports without error handling
- Include optional dependencies in the main `dependencies` list
### Integration Structure
- Place LLM clients in `graphiti_core/llm_client/`
- Place embedding clients in `graphiti_core/embedder/`
- Place database drivers in `graphiti_core/driver/`
- Follow existing naming conventions (e.g., `your_service_client.py`)
### Testing
- Add comprehensive tests in the appropriate `tests/` subdirectory
- Mark integration tests with `_int` suffix if they require external services
- Include both unit tests and integration tests where applicable
# Questions?
Stuck on a contribution or have a half-formed idea? Come say hello in our [Discord server](https://discord.com/invite/W8Kw6bsgXQ). Whether you're ready to contribute or just want to learn more, we're happy to have you! It's faster than GitHub issues and you'll find both maintainers and fellow contributors ready to help.

View file

@ -137,7 +137,18 @@ or
uv add graphiti-core
```
You can also install optional LLM providers as extras:
### Installing with FalkorDB Support
If you plan to use FalkorDB as your graph database backend, install with the FalkorDB extra:
```bash
pip install graphiti-core[falkordb]
# or with uv
uv add graphiti-core[falkordb]
```
### You can also install optional LLM providers as extras:
```bash
# Install with Anthropic support
@ -151,6 +162,9 @@ pip install graphiti-core[google-genai]
# Install with multiple providers
pip install graphiti-core[anthropic,groq,google-genai]
# Install with FalkorDB and LLM providers
pip install graphiti-core[falkordb,anthropic,google-genai]
```
## Quick Start

View file

@ -15,7 +15,6 @@ limitations under the License.
"""
from .client import CrossEncoderClient
from .gemini_reranker_client import GeminiRerankerClient
from .openai_reranker_client import OpenAIRerankerClient
__all__ = ['CrossEncoderClient', 'GeminiRerankerClient', 'OpenAIRerankerClient']
__all__ = ['CrossEncoderClient', 'OpenAIRerankerClient']

View file

@ -15,8 +15,18 @@ limitations under the License.
"""
import asyncio
from typing import TYPE_CHECKING
from sentence_transformers import CrossEncoder
if TYPE_CHECKING:
from sentence_transformers import CrossEncoder
else:
try:
from sentence_transformers import CrossEncoder
except ImportError:
raise ImportError(
'sentence-transformers is required for BGERerankerClient. '
'Install it with: pip install graphiti-core[sentence-transformers]'
) from None
from graphiti_core.cross_encoder.client import CrossEncoderClient

View file

@ -16,24 +16,38 @@ limitations under the License.
import logging
import re
from google import genai # type: ignore
from google.genai import types # type: ignore
from typing import TYPE_CHECKING
from ..helpers import semaphore_gather
from ..llm_client import LLMConfig, RateLimitError
from .client import CrossEncoderClient
if TYPE_CHECKING:
from google import genai
from google.genai import types
else:
try:
from google import genai
from google.genai import types
except ImportError:
raise ImportError(
'google-genai is required for GeminiRerankerClient. '
'Install it with: pip install graphiti-core[google-genai]'
) from None
logger = logging.getLogger(__name__)
DEFAULT_MODEL = 'gemini-2.5-flash-lite-preview-06-17'
class GeminiRerankerClient(CrossEncoderClient):
"""
Google Gemini Reranker Client
"""
def __init__(
self,
config: LLMConfig | None = None,
client: genai.Client | None = None,
client: 'genai.Client | None' = None,
):
"""
Initialize the GeminiRerankerClient with the provided configuration and client.

View file

@ -22,7 +22,7 @@ import openai
from openai import AsyncAzureOpenAI, AsyncOpenAI
from ..helpers import semaphore_gather
from ..llm_client import LLMConfig, RateLimitError
from ..llm_client import LLMConfig, OpenAIClient, RateLimitError
from ..prompts import Message
from .client import CrossEncoderClient
@ -35,7 +35,7 @@ class OpenAIRerankerClient(CrossEncoderClient):
def __init__(
self,
config: LLMConfig | None = None,
client: AsyncOpenAI | AsyncAzureOpenAI | None = None,
client: AsyncOpenAI | AsyncAzureOpenAI | OpenAIClient | None = None,
):
"""
Initialize the OpenAIRerankerClient with the provided configuration and client.
@ -45,7 +45,7 @@ class OpenAIRerankerClient(CrossEncoderClient):
Args:
config (LLMConfig | None): The configuration for the LLM client, including API key, model, base URL, temperature, and max tokens.
client (AsyncOpenAI | AsyncAzureOpenAI | None): An optional async client instance to use. If not provided, a new AsyncOpenAI client is created.
client (AsyncOpenAI | AsyncAzureOpenAI | OpenAIClient | None): An optional async client instance to use. If not provided, a new AsyncOpenAI client is created.
"""
if config is None:
config = LLMConfig()
@ -53,6 +53,8 @@ class OpenAIRerankerClient(CrossEncoderClient):
self.config = config
if client is None:
self.client = AsyncOpenAI(api_key=config.api_key, base_url=config.base_url)
elif isinstance(client, OpenAIClient):
self.client = client.client
else:
self.client = client

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
from falkordb import FalkorDB
from neo4j import Neo4jDriver
__all__ = ['Neo4jDriver', 'FalkorDB']
__all__ = ['Neo4jDriver']

View file

@ -16,10 +16,21 @@ limitations under the License.
import logging
from datetime import datetime
from typing import Any
from typing import TYPE_CHECKING, Any
from falkordb import Graph as FalkorGraph # type: ignore
from falkordb.asyncio import FalkorDB # type: ignore
if TYPE_CHECKING:
from falkordb import Graph as FalkorGraph
from falkordb.asyncio import FalkorDB
else:
try:
from falkordb import Graph as FalkorGraph
from falkordb.asyncio import FalkorDB
except ImportError:
# If falkordb is not installed, raise an ImportError
raise ImportError(
'falkordb is required for FalkorDriver. '
'Install it with: pip install graphiti-core[falkordb]'
) from None
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
from graphiti_core.helpers import DEFAULT_DATABASE

View file

@ -15,9 +15,21 @@ limitations under the License.
"""
from collections.abc import Iterable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from google import genai
from google.genai import types
else:
try:
from google import genai
from google.genai import types
except ImportError:
raise ImportError(
'google-genai is required for GeminiEmbedder. '
'Install it with: pip install graphiti-core[google-genai]'
) from None
from google import genai # type: ignore
from google.genai import types # type: ignore
from pydantic import Field
from .client import EmbedderClient, EmbedderConfig
@ -34,16 +46,27 @@ class GeminiEmbedder(EmbedderClient):
"""
Google Gemini Embedder Client
"""
def __init__(
self,
config: GeminiEmbedderConfig | None = None,
client: 'genai.Client | None' = None,
):
"""
Initialize the GeminiEmbedder with the provided configuration and client.
def __init__(self, config: GeminiEmbedderConfig | None = None):
Args:
config (GeminiEmbedderConfig | None): The configuration for the GeminiEmbedder, including API key, model, base URL, temperature, and max tokens.
client (genai.Client | None): An optional async client instance to use. If not provided, a new genai.Client is created.
"""
if config is None:
config = GeminiEmbedderConfig()
self.config = config
# Configure the Gemini API
self.client = genai.Client(
api_key=config.api_key,
)
if client is None:
self.client = genai.Client(api_key=config.api_key)
else:
self.client = client
async def create(
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]

View file

@ -15,8 +15,19 @@ limitations under the License.
"""
from collections.abc import Iterable
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import voyageai
else:
try:
import voyageai
except ImportError:
raise ImportError(
'voyageai is required for VoyageAIEmbedderClient. '
'Install it with: pip install graphiti-core[voyageai]'
) from None
import voyageai # type: ignore
from pydantic import Field
from .client import EmbedderClient, EmbedderConfig

View file

@ -19,11 +19,8 @@ import logging
import os
import typing
from json import JSONDecodeError
from typing import Literal
from typing import TYPE_CHECKING, Literal
import anthropic
from anthropic import AsyncAnthropic
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
from pydantic import BaseModel, ValidationError
from ..prompts.models import Message
@ -31,6 +28,22 @@ from .client import LLMClient
from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
from .errors import RateLimitError, RefusalError
if TYPE_CHECKING:
import anthropic
from anthropic import AsyncAnthropic
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
else:
try:
import anthropic
from anthropic import AsyncAnthropic
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
except ImportError:
raise ImportError(
'anthropic is required for AnthropicClient. '
'Install it with: pip install graphiti-core[anthropic]'
) from None
logger = logging.getLogger(__name__)
AnthropicModel = Literal[

View file

@ -17,10 +17,8 @@ limitations under the License.
import json
import logging
import typing
from typing import ClassVar
from typing import TYPE_CHECKING, ClassVar
from google import genai # type: ignore
from google.genai import types # type: ignore
from pydantic import BaseModel
from ..prompts.models import Message
@ -28,6 +26,21 @@ from .client import MULTILINGUAL_EXTRACTION_RESPONSES, LLMClient
from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
from .errors import RateLimitError
if TYPE_CHECKING:
from google import genai
from google.genai import types
else:
try:
from google import genai
from google.genai import types
except ImportError:
# If gemini client is not installed, raise an ImportError
raise ImportError(
'google-genai is required for GeminiClient. '
'Install it with: pip install graphiti-core[google-genai]'
) from None
logger = logging.getLogger(__name__)
DEFAULT_MODEL = 'gemini-2.5-flash'
@ -63,6 +76,7 @@ class GeminiClient(LLMClient):
cache: bool = False,
max_tokens: int = DEFAULT_MAX_TOKENS,
thinking_config: types.ThinkingConfig | None = None,
client: 'genai.Client | None' = None,
):
"""
Initialize the GeminiClient with the provided configuration, cache setting, and optional thinking config.
@ -72,7 +86,7 @@ class GeminiClient(LLMClient):
cache (bool): Whether to use caching for responses. Defaults to False.
thinking_config (types.ThinkingConfig | None): Optional thinking configuration for models that support it.
Only use with models that support thinking (gemini-2.5+). Defaults to None.
client (genai.Client | None): An optional async client instance to use. If not provided, a new genai.Client is created.
"""
if config is None:
config = LLMConfig()
@ -80,10 +94,12 @@ class GeminiClient(LLMClient):
super().__init__(config, cache)
self.model = config.model
# Configure the Gemini API
self.client = genai.Client(
api_key=config.api_key,
)
if client is None:
self.client = genai.Client(api_key=config.api_key)
else:
self.client = client
self.max_tokens = max_tokens
self.thinking_config = thinking_config

View file

@ -17,10 +17,21 @@ limitations under the License.
import json
import logging
import typing
from typing import TYPE_CHECKING
import groq
from groq import AsyncGroq
from groq.types.chat import ChatCompletionMessageParam
if TYPE_CHECKING:
import groq
from groq import AsyncGroq
from groq.types.chat import ChatCompletionMessageParam
else:
try:
import groq
from groq import AsyncGroq
from groq.types.chat import ChatCompletionMessageParam
except ImportError:
raise ImportError(
'groq is required for GroqClient. Install it with: pip install graphiti-core[groq]'
) from None
from pydantic import BaseModel
from ..prompts.models import Message

View file

@ -31,9 +31,9 @@ services:
neo4j:
condition: service_healthy
environment:
- NEO4J_URI=bolt://neo4j:7687
- NEO4J_USER=neo4j
- NEO4J_PASSWORD=demodemo
- NEO4J_URI=${NEO4J_URI:-bolt://neo4j:7687}
- NEO4J_USER=${NEO4J_USER:-neo4j}
- NEO4J_PASSWORD=${NEO4J_PASSWORD:-demodemo}
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MODEL_NAME=${MODEL_NAME}
- PATH=/root/.local/bin:${PATH}

View file

@ -1,7 +1,7 @@
[project]
name = "graphiti-core"
description = "A temporal graph building library"
version = "0.15.0"
version = "0.15.1"
authors = [
{ "name" = "Paul Paliychuk", "email" = "paul@getzep.com" },
{ "name" = "Preston Rasmussen", "email" = "preston@getzep.com" },
@ -29,12 +29,15 @@ Repository = "https://github.com/getzep/graphiti"
anthropic = ["anthropic>=0.49.0"]
groq = ["groq>=0.2.0"]
google-genai = ["google-genai>=1.8.0"]
falkord-db = ["falkordb>=1.1.2,<2.0.0"]
falkordb = ["falkordb>=1.1.2,<2.0.0"]
voyageai = ["voyageai>=0.2.3"]
sentence-transformers = ["sentence-transformers>=3.2.1"]
dev = [
"pyright>=1.1.380",
"groq>=0.2.0",
"anthropic>=0.49.0",
"google-genai>=1.8.0",
"falkordb>=1.1.2,<2.0.0",
"ipykernel>=6.29.5",
"jupyterlab>=4.2.4",
"diskcache-stubs>=5.6.3.6.20240818",

View file

@ -183,6 +183,22 @@
"created_at": "2025-07-02T15:24:23Z",
"repoId": 840056306,
"pullRequestNo": 664
},
{
"name": "dev-mirzabicer",
"id": 90691873,
"comment_id": 3035836506,
"created_at": "2025-07-04T11:47:08Z",
"repoId": 840056306,
"pullRequestNo": 672
},
{
"name": "zeroasterisk",
"id": 23422,
"comment_id": 3040716245,
"created_at": "2025-07-06T03:41:19Z",
"repoId": 840056306,
"pullRequestNo": 679
}
]
}

3911
uv.lock generated

File diff suppressed because it is too large Load diff