Skip to content

Conversation

@galshubeli
Copy link
Contributor

@galshubeli galshubeli commented Nov 24, 2024

Summary by CodeRabbit

  • New Features

    • Introduced support for the LiteLLM model for chat completions.
    • Added a new Knowledge Graph testing framework for LiteLLM.
    • Expanded SDK capabilities to include support for Ollama and LiteLLM models.
  • Bug Fixes

    • Enhanced error handling in the ontology creation process and JSON processing.
  • Documentation

    • Updated README to reflect new model support and SDK capabilities.
    • Modified .env.template to include new environment variable declarations.
  • Chores

    • Added optional dependency for LiteLLM in project configuration.
    • Updated wordlist with new entries.

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2024

Walkthrough

The pull request introduces a new module litellm.py in the GraphRAG SDK that provides support for LiteLLM, a framework for large language model inference. The changes include adding new LiteModel and LiteModelChatSession classes to handle model interactions, updating the project's dependencies in pyproject.toml, modifying the environment template, and expanding documentation in the README. The modifications enhance the SDK's flexibility by supporting additional language models and improving error handling in ontology creation.

Changes

File Changes
graphrag_sdk/models/litellm.py Added new LiteModel and LiteModelChatSession classes for LiteLLM model integration, including various methods for model interaction and chat management.
tests/test_kg_litellm_openai.py Added test class TestKGLiteLLM for validating Knowledge Graph creation with LiteLLM.
pyproject.toml Added optional litellm dependency and updated extras configuration.
.env.template Added new environment variables for Gemini, Ollama, and Azure API configurations.
.wordlist.txt Added entries for "Cypher" and "LiteLLM".
README.md Updated LLMs integration section to include LiteLLM and Ollama support.
graphrag_sdk/steps/create_ontology_step.py Improved error handling in ontology creation process.

Sequence Diagram

sequenceDiagram
    participant User
    participant LiteModel
    participant LiteLLM
    participant OllamaClient

    User->>LiteModel: Initialize with model name
    LiteModel->>LiteModel: Check API keys
    LiteModel->>LiteLLM: Validate model
    alt Model is Ollama
        LiteModel->>OllamaClient: Check/Pull model
    end
    User->>LiteModel: Start chat session
    LiteModel->>LiteModelChatSession: Create session
    User->>LiteModelChatSession: Send message
    LiteModelChatSession->>LiteLLM: Request completion
    LiteLLM-->>LiteModelChatSession: Return response
    LiteModelChatSession-->>User: Deliver response
Loading

Poem

🐰 A Rabbit's Ode to LiteLLM 🤖

In the realm of models, a new star shines bright,
LiteLLM joins the dance with linguistic might!
Ollama, Gemini, all welcome to play,
Our SDK grows smarter with each passing day.
Code hopping forward, no boundaries in sight! 🌟

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@galshubeli galshubeli requested a review from swilly22 November 24, 2024 21:48
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Outside diff range and nitpick comments (4)
tests/test_kg_litellm_openai.py (1)

97-102: Enhance deletion test verification

The deletion test could be more thorough in verifying cleanup.

Add additional verification:

     def test_kg_delete(self):
         self.kg.delete()
 
         db = FalkorDB()
         graphs = db.list_graphs()
         self.assertNotIn(self.graph_name, graphs)
+        # Verify no orphaned nodes remain
+        actor_count = db.query("MATCH (a:Actor) RETURN COUNT(a)")[0][0]
+        movie_count = db.query("MATCH (m:Movie) RETURN COUNT(m)")[0][0]
+        self.assertEqual(actor_count + movie_count, 0, "Orphaned nodes found after deletion")
graphrag_sdk/models/litellm.py (3)

85-85: Avoid using magic numbers; define a constant for message truncation limit

The truncation limit of 14385 characters in message[:14385] is a magic number and may not be immediately clear to future developers. Consider defining a constant or making it a configurable parameter to improve code readability and maintainability.

Apply this diff to define a constant:

+# At the top of the file or class, define the constant
+MAX_MESSAGE_LENGTH = 14385

 # In the `send_message` method:
     def send_message(self, message: str, output_method: OutputMethod = OutputMethod.DEFAULT) -> GenerationResponse:
         generation_config = self._get_generation_config(output_method)
         prompt = []
         prompt.extend(self._history)
-        prompt.append({"role": "user", "content": message[:14385]})
+        prompt.append({"role": "user", "content": message[:MAX_MESSAGE_LENGTH]})
         response = completion(
             model=self._model.model_name,
             messages=prompt,

29-31: Update return type annotation to LiteLLMGenerativeModel for method chaining

The with_system_instruction method returns self, which is an instance of LiteLLMGenerativeModel. Updating the return type annotation to LiteLLMGenerativeModel improves clarity and supports method chaining with subclass methods.

Apply this diff to update the return type:

     def with_system_instruction(self, system_instruction: str) -> "GenerativeModel":
-        self.system_instruction = system_instruction
-        return self
+        self.system_instruction = system_instruction
+        return self

+    def with_system_instruction(self, system_instruction: str) -> "LiteLLMGenerativeModel":
+        self.system_instruction = system_instruction
+        return self

96-103: Avoid mutating shared configuration dictionary

In _get_generation_config, modifying the dictionary returned by self._model.generation_config.to_json() could inadvertently affect other parts of the code if the same dictionary is used elsewhere. To prevent side effects, make a copy of the dictionary before modifying it.

Apply this diff to make a copy of the configuration:

     def _get_generation_config(self, output_method: OutputMethod):
-        config = self._model.generation_config.to_json()
+        config = self._model.generation_config.to_json().copy()
         if output_method == OutputMethod.JSON:
             config['temperature'] = 0
             config['response_format'] = { "type": "json_object" }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a2bbf86 and c4c88ee.

📒 Files selected for processing (2)
  • graphrag_sdk/models/litellm.py (1 hunks)
  • tests/test_kg_litellm_openai.py (1 hunks)
🔇 Additional comments (1)
tests/test_kg_litellm_openai.py (1)

80-84: Verify test data file contents and access

The test processes an external file which could pose security risks if not properly validated.

✅ Verification successful

Let me check the actual content of the file to better understand what kind of data we're dealing with.


Test data file is safe for use

The file madoff.txt contains publicly available IMDb content about a documentary series. It's non-sensitive test data that's consistently used across multiple test files for knowledge graph processing. The file has appropriate read-only permissions and contains no security-sensitive information.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check test data file permissions and content
echo "Checking test data file..."
if [ -f "tests/data/madoff.txt" ]; then
    ls -l tests/data/madoff.txt
    echo "File contents preview:"
    head -n 5 tests/data/madoff.txt
else
    echo "Warning: Test data file not found"
fi

# Check for other potential test data files that might contain sensitive information
echo "Checking for other data files..."
find tests/data -type f -name "*.txt" -exec ls -l {} \;

Length of output: 513


Script:

#!/bin/bash
# Get full content of the test file
echo "Full content of madoff.txt:"
cat tests/data/madoff.txt

# Check if this file is used in other tests
echo -e "\nChecking file usage in tests:"
rg "madoff.txt" tests/

Length of output: 6015

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c4c88ee and 666dced.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • pyproject.toml (1 hunks)
🔇 Additional comments (1)
pyproject.toml (1)

29-29: Verify litellm version constraints

Let's verify if version ^1.52.14 is the latest stable version and check for any security advisories.

✅ Verification successful

litellm version 1.52.14 is up-to-date and secure

The specified version 1.52.14 is currently the latest stable version (released on November 22, 2024) and is well above all known vulnerability patches:

  • SSRF vulnerability (patched in 1.44.8)
  • Access control vulnerability (patched in 1.40.15)
  • Remote code execution vulnerability (patched in 1.40.16)
  • File deletion vulnerability (patched in 1.35.36)
  • SQL injection vulnerability (patched in 1.40.0)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check latest version and security advisories for litellm

# Check PyPI for latest versions
echo "Latest version from PyPI:"
curl -s https://pypi.org/pypi/litellm/json | jq -r '.info.version'

# Check release date of current version
echo -e "\nRelease date of specified version:"
curl -s https://pypi.org/pypi/litellm/1.52.14/json | jq -r '.urls[0].upload_time'

# Check for security advisories
echo -e "\nSecurity advisories:"
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: PIP, package: "litellm") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 1814

@gkorland gkorland requested a review from Copilot November 24, 2024 22:06
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 suggestions.

Comments skipped due to low confidence (1)

tests/test_kg_litellm_openai.py:95

  • Use 'self.assertGreater' instead of 'assert' for better readability and consistency with other tests.
assert num_actors > 10, "The number of actors found should be greater than 10"

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (3)
graphrag_sdk/models/litellm.py (3)

36-47: Enhance method chaining by returning a new instance

The with_system_instruction method modifies the instance in place and returns self. To make the model immutable and support method chaining without side effects, consider returning a new instance with the updated system_instruction.

Apply this diff to return a new instance:

 def with_system_instruction(self, system_instruction: str) -> "GenerativeModel":
     self.system_instruction = system_instruction
-    return self
+    return LiteLLMGenerativeModel(
+        model_name=self.model_name,
+        generation_config=self.generation_config,
+        system_instruction=system_instruction,
+    )

122-136: Ensure args are utilized or remove the parameter

The __init__ method accepts an args parameter, but it is not used anywhere in the class. This might cause confusion or indicate incomplete implementation. Consider using args within the class or removing the parameter if it's unnecessary.


159-159: Avoid hardcoding magic numbers

Using a hardcoded value 14385 in message[:14385] can lead to maintainability issues and might not be clear to other developers. Consider defining a constant with a meaningful name or adding a comment explaining its significance.

Apply this diff to define a constant:

+MAX_MESSAGE_LENGTH = 14385  # Maximum allowed message length

 def send_message(self, message: str, output_method: OutputMethod = OutputMethod.DEFAULT) -> GenerationResponse:
     generation_config = self._adjust_generation_config(output_method)
-    self._chat_history.append({"role": "user", "content": message[:14385]})
+    self._chat_history.append({"role": "user", "content": message[:MAX_MESSAGE_LENGTH]})
     response = completion(
         model=self._model.model_name,
         messages=self._chat_history,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 893a704 and f7d7fd8.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • graphrag_sdk/models/litellm.py (1 hunks)
🔇 Additional comments (2)
graphrag_sdk/models/litellm.py (2)

186-216: LGTM

The delete_last_message method correctly handles the removal of the last message exchange, preserving the system message when present.


169-184: Verify response_format support in LiteLLM API

In the _adjust_generation_config method, the response_format parameter is set when output_method is OutputMethod.JSON. Ensure that the LiteLLM completion function supports the response_format parameter; otherwise, this could lead to unexpected errors.

Run the following script to check if response_format is a valid parameter:

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
graphrag_sdk/models/litellm.py (2)

38-44: Improve provider parsing logic

The current provider parsing logic could be more robust and maintainable.

Consider using a dictionary for provider mapping:

-        if "/" in model_name:
-            self.provider = model_name.split("/")[0]
-            self.model_name = model_name.split("/")[1]
-        else:
-            self.provider = "openai"
-            self.model_name = model_name
+        PROVIDER_MAP = {
+            "openai": ["gpt", "text-"],
+            "azure": ["azure"],
+            "gemini": ["gemini"],
+            "ollama": ["ollama"]
+        }
+        
+        parts = model_name.split("/", 1)
+        if len(parts) == 2:
+            self.provider, self.model_name = parts
+        else:
+            self.model_name = model_name
+            self.provider = next(
+                (provider for provider, prefixes in PROVIDER_MAP.items() 
+                 if any(self.model_name.startswith(prefix) for prefix in prefixes)),
+                "openai"
+            )

50-72: Enhance credentials validation

The credentials validation could be more robust and maintainable.

Consider using a more structured approach:

     def credentials_validation(self) -> None:
+        REQUIRED_CREDENTIALS = {
+            "openai": [("OPENAI_API_KEY", "OpenAI API key")],
+            "azure": [
+                ("AZURE_API_KEY", "Azure API key"),
+                ("AZURE_API_BASE", "Azure API base URL"),
+                ("AZURE_API_VERSION", "Azure API version")
+            ],
+            "gemini": [("GEMINI_API_KEY", "Gemini API key")],
+            "ollama": []
+        }
+        
+        if self.provider not in REQUIRED_CREDENTIALS:
+            raise ValueError(f"Unsupported provider: {self.provider}")
+            
+        missing_credentials = [
+            desc for env_var, desc in REQUIRED_CREDENTIALS[self.provider]
+            if not os.getenv(env_var)
+        ]
+        
+        if missing_credentials:
+            raise ValueError(
+                f"Missing {self.provider} credentials: {', '.join(missing_credentials)}"
+            )
+            
+        if self.provider == "ollama":
+            self.ollama_client = Client()
+            self.check_and_pull_model()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f7d7fd8 and e9376a2.

📒 Files selected for processing (2)
  • graphrag_sdk/models/litellm.py (1 hunks)
  • tests/test_kg_litellm_openai.py (1 hunks)
🔇 Additional comments (8)
tests/test_kg_litellm_openai.py (4)

97-102: LGTM!

The test method properly verifies the deletion of the knowledge graph.


14-17: 🛠️ Refactor suggestion

Consider test-specific logging configuration

The current setup has potential issues:

  1. Setting logging to DEBUG level globally could expose sensitive information in test logs
  2. Loading environment variables at module level could affect other tests

Consider moving these configurations into the test class setup:

-load_dotenv()
-logging.basicConfig(level=logging.DEBUG)
-logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
+
+@classmethod
+def setUpClass(cls):
+    load_dotenv()
+    cls.log_handler = logging.StreamHandler()
+    cls.log_handler.setLevel(logging.INFO)
+    logger.addHandler(cls.log_handler)

Likely invalid or redundant comment.


71-71: 🛠️ Refactor suggestion

Add error handling for model initialization

The model initialization lacks error handling and uses a hardcoded model name. This could make tests brittle and fail silently if the model is unavailable.

Consider making the model configurable:

-        model = LiteModel(model_name="gpt-4o")
+        model_name = os.getenv("TEST_LLM_MODEL", "gpt-4o")
+        try:
+            model = LiteModel(model_name=model_name)
+        except Exception as e:
+            raise unittest.SkipTest(f"Failed to initialize LLM model: {e}")

Likely invalid or redundant comment.


80-95: 🛠️ Refactor suggestion

Improve test reliability and validation

The current test implementation has several reliability issues:

  1. Uses a hardcoded file path which could break in different environments
  2. Relies on regex parsing of LLM responses which could be unstable
  3. Uses an arbitrary success criterion (>10 actors)

Consider these improvements:

-        file_path = "tests/data/madoff.txt"
+        file_path = os.path.join(os.path.dirname(__file__), "data", "madoff.txt")
+        self.assertTrue(os.path.exists(file_path), "Test data file not found")

         sources = [Source(file_path)]
         self.kg.process_sources(sources)

         chat = self.kg.chat_session()
-        answer = chat.send_message("How many actors acted in a movie?")
-        answer = answer['response']
+        try:
+            answer = chat.send_message("Count the total number of unique Actor nodes in the graph")
+            answer = answer['response']
+        except Exception as e:
+            self.fail(f"Chat session failed: {e}")

-        actors_count = re.findall(r'\d+', answer)
-        num_actors = 0 if len(actors_count) == 0 else int(actors_count[0])
+        # Verify through direct graph query instead of parsing LLM response
+        db = FalkorDB()
+        actual_count = db.query(f"MATCH (a:Actor) RETURN COUNT(DISTINCT a)")[0][0]
+        self.assertGreater(actual_count, 0, "No actors found in the graph")

Likely invalid or redundant comment.

graphrag_sdk/models/litellm.py (4)

180-199: LGTM!

The chat session implementation properly manages the chat history and handles system instructions correctly.


160-177: 🛠️ Refactor suggestion

Add input validation to from_json method

The method directly accesses dictionary keys without validation, which could raise KeyError.

Add proper input validation:

     @staticmethod
     def from_json(json: dict) -> "GenerativeModel":
+        required_fields = ['model_name', 'generation_config']
+        missing_fields = [field for field in required_fields if field not in json]
+        if missing_fields:
+            raise ValueError(f"Missing required fields: {', '.join(missing_fields)}")
+            
         return LiteModel(
-            json["model_name"],
+            json['model_name'],
             generation_config=GenerativeModelConfig.from_json(
-                json["generation_config"]
+                json['generation_config']
             ),
-            system_instruction=json["system_instruction"],
+            system_instruction=json.get('system_instruction'),
         )

Likely invalid or redundant comment.


210-230: 🛠️ Refactor suggestion

Add error handling and message validation

The method lacks error handling for the completion call and message length validation.

Add proper error handling and validation:

+    MAX_MESSAGE_LENGTH = 4096  # Adjust based on model limits
+
     def send_message(self, message: str, output_method: OutputMethod = OutputMethod.DEFAULT) -> GenerationResponse:
+        if not message.strip():
+            raise ValueError("Message cannot be empty")
+            
+        if len(message) > self.MAX_MESSAGE_LENGTH:
+            logger.warning(f"Message truncated from {len(message)} to {self.MAX_MESSAGE_LENGTH} characters")
+            message = message[:self.MAX_MESSAGE_LENGTH]
+            
         generation_config = self._adjust_generation_config(output_method)
         self._chat_history.append({"role": "user", "content": message})
-        response = completion(
-            model=self._model.model,
-            messages=self._chat_history,
-            **generation_config
+        try:
+            response = completion(
+                model=self._model.model,
+                messages=self._chat_history,
+                **generation_config
             )
-        content = self._model.parse_generate_content_response(response)
+            content = self._model.parse_generate_content_response(response)
+        except Exception as e:
+            logger.error(f"Error during message completion: {e}")
+            raise RuntimeError(f"Failed to generate response: {e}") from e
+            
         self._chat_history.append({"role": "assistant", "content": content.text})
         return content

Likely invalid or redundant comment.


124-145: 🛠️ Refactor suggestion

Add error handling for unexpected response structure

The method assumes that response.choices[0].message.content and response.choices[0].finish_reason are always present. This could raise exceptions with malformed responses.

Add proper error handling:

     def parse_generate_content_response(self, response: any) -> GenerationResponse:
+        if not hasattr(response, 'choices') or not response.choices:
+            raise ValueError("Invalid response: missing choices")
+            
+        choice = response.choices[0]
+        if not hasattr(choice, 'message') or not hasattr(choice.message, 'content'):
+            raise ValueError("Invalid response: missing message content")
+            
+        if not hasattr(choice, 'finish_reason'):
+            raise ValueError("Invalid response: missing finish reason")
+            
         return GenerationResponse(
-            text=response.choices[0].message.content,
+            text=choice.message.content,
             finish_reason=(
                 FinishReason.STOP
-                if response.choices[0].finish_reason == "stop"
+                if choice.finish_reason == "stop"
                 else (
                     FinishReason.MAX_TOKENS
-                    if response.choices[0].finish_reason == "length"
+                    if choice.finish_reason == "length"
                     else FinishReason.OTHER
                 )
             ),
         )

Likely invalid or redundant comment.

@galshubeli galshubeli requested a review from gkorland December 8, 2024 13:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (3)
tests/test_kg_litellm_openai.py (1)

19-77: ⚠️ Potential issue

Add tearDown method for cleanup

The test class is missing cleanup code to remove test artifacts.

Add a tearDown method as suggested in the previous review:

@classmethod
def tearDownClass(cls):
    try:
        # Clean up the knowledge graph
        if hasattr(cls, 'kg'):
            cls.kg.delete()
        
        # Remove logging handler
        if hasattr(cls, 'log_handler'):
            logger.removeHandler(cls.log_handler)
    except Exception as e:
        logger.warning(f"Cleanup failed: {e}")
graphrag_sdk/models/litellm.py (2)

165-182: ⚠️ Potential issue

Handle missing keys in from_json method.

The method directly accesses dictionary keys without validation, which could lead to KeyError exceptions.

Apply this diff to add proper error handling:

     @staticmethod
     def from_json(json: dict) -> "GenerativeModel":
+        model_name = json.get("model_name")
+        generation_config_json = json.get("generation_config")
+        system_instruction = json.get("system_instruction")
+
+        if model_name is None or generation_config_json is None:
+            raise ValueError("Invalid input: 'model_name' and 'generation_config' are required")
+
         return LiteModel(
-            json["model_name"],
-            generation_config=GenerativeModelConfig.from_json(
-                json["generation_config"]
-            ),
-            system_instruction=json["system_instruction"],
+            model_name,
+            generation_config=GenerativeModelConfig.from_json(generation_config_json),
+            system_instruction=system_instruction,
         )

129-150: ⚠️ Potential issue

Add error handling for unexpected response structure.

The parse_generate_content_response method assumes the response structure without validation.

Apply this diff to add proper error handling:

     def parse_generate_content_response(self, response: any) -> GenerationResponse:
+        if not response.choices or not hasattr(response.choices[0], 'message') or not hasattr(response.choices[0].message, 'content'):
+            raise ValueError("Invalid response format: 'content' not found in response.")
+        if not hasattr(response.choices[0], 'finish_reason'):
+            raise ValueError("Invalid response format: 'finish_reason' not found in response.")
         return GenerationResponse(
             text=response.choices[0].message.content,
             finish_reason=(
🧹 Nitpick comments (3)
tests/test_kg_litellm_openai.py (1)

4-4: Remove unused import

The FalkorDB import is not used in the test class.

-from falkordb import FalkorDB
🧰 Tools
🪛 Ruff (0.8.2)

4-4: falkordb.FalkorDB imported but unused

Remove unused import: falkordb.FalkorDB

(F401)

graphrag_sdk/steps/create_ontology_step.py (1)

80-81: Add validation for empty or missing document content

The code should validate document content before processing.

+        if not document.content:
+            logger.warning(f"Empty document content: {document}")
+            return o
+
         text = document.content[: self.config["max_input_tokens"]]
graphrag_sdk/models/litellm.py (1)

258-288: Consider adding validation for chat history integrity.

The delete_last_message method modifies the chat history without validating its structure.

Consider adding validation to ensure the chat history maintains a valid structure:

     def delete_last_message(self):
+        def is_valid_message(msg: dict) -> bool:
+            return isinstance(msg, dict) and "role" in msg and "content" in msg
+
         # Keep at least the system message if present
         min_length = 1 if self._model.system_instruction else 0
-        if len(self._chat_history) - 2 >= min_length:
+        if (len(self._chat_history) >= 2 and 
+            all(is_valid_message(msg) for msg in self._chat_history[-2:])):
             self._chat_history.pop()
             self._chat_history.pop()
         else:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9376a2 and b38a8d7.

📒 Files selected for processing (7)
  • .env.template (1 hunks)
  • .wordlist.txt (1 hunks)
  • README.md (2 hunks)
  • graphrag_sdk/models/litellm.py (1 hunks)
  • graphrag_sdk/steps/create_ontology_step.py (1 hunks)
  • graphrag_sdk/steps/extract_data_step.py (2 hunks)
  • tests/test_kg_litellm_openai.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .wordlist.txt
🧰 Additional context used
🪛 Ruff (0.8.2)
graphrag_sdk/steps/create_ontology_step.py

78-78: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/test_kg_litellm_openai.py

4-4: falkordb.FalkorDB imported but unused

Remove unused import: falkordb.FalkorDB

(F401)

graphrag_sdk/steps/extract_data_step.py

98-98: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


203-203: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

graphrag_sdk/models/litellm.py

236-236: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (4)
.env.template (1)

10-11: LGTM: GEMINI_API_KEY addition for LiteLLM integration

The addition of GEMINI_API_KEY is appropriate for the LiteLLM integration.

tests/test_kg_litellm_openai.py (2)

80-80: ⚠️ Potential issue

Use path-independent file resolution

Hardcoded file paths can break when tests are run from different directories.

-file_path = "tests/data/madoff.txt"
+import os
+file_path = os.path.join(os.path.dirname(__file__), "data", "madoff.txt")

Likely invalid or redundant comment.


92-95: 🛠️ Refactor suggestion

Enhance assertion with proper error messages

The current assertion lacks proper error messages and validation.

-actors_count = re.findall(r'\d+', answer)
-num_actors = 0 if len(actors_count) == 0 else int(actors_count[0])
-
-assert num_actors > 10, "The number of actors found should be greater than 10"
+try:
+    actors_count = re.findall(r'\d+', response)
+    if not actors_count:
+        raise ValueError("No numeric value found in response")
+    num_actors = int(actors_count[0])
+    self.assertGreater(
+        num_actors, 
+        10, 
+        f"Expected more than 10 actors, but found {num_actors}"
+    )
+except ValueError as e:
+    self.fail(f"Failed to parse actor count: {e}")

Likely invalid or redundant comment.

README.md (1)

16-16: LGTM!

The addition of LiteLLM to the list of supported LLMs is accurate and well-documented.

@galshubeli galshubeli requested a review from swilly22 December 15, 2024 13:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98a9714 and 24c1113.

📒 Files selected for processing (1)
  • graphrag_sdk/models/litellm.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
graphrag_sdk/models/litellm.py

241-241: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (3)
graphrag_sdk/models/litellm.py (3)

134-155: Add error handling for malformed responses

The parse_generate_content_response method assumes a specific response structure without proper validation.


171-187: Add error handling for missing keys in from_json

The from_json method needs to handle missing keys to prevent KeyError exceptions.


49-54: 🛠️ Refactor suggestion

Improve provider handling to prevent confusion

The current implementation assumes OpenAI as the default provider when no provider is specified, which could lead to confusion and potential errors.

Apply this diff to make the provider explicit:

         if "/" in model_name:
             self.provider = model_name.split("/")[0]
             self.model_name = model_name.split("/")[1]
         else:
-            self.provider = "openai"
-            self.model_name = model_name
+            raise ValueError(
+                "Provider must be explicitly specified in model_name. "
+                "Format: <provider>/<model_name> (e.g., openai/gpt-4)"
+            )

Likely invalid or redundant comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
graphrag_sdk/models/litellm.py (3)

142-153: 🛠️ Refactor suggestion

Add error handling for unexpected response structure

The parse_generate_content_response method assumes that response.choices[0].message.content and response.choices[0].finish_reason are always present. If the response is malformed or if choices is empty, this could raise an IndexError or AttributeError.

Apply this diff to add error handling:

     def parse_generate_content_response(self, response: any) -> GenerationResponse:
+        if not response.choices or not hasattr(response.choices[0], 'message') or not hasattr(response.choices[0].message, 'content'):
+            raise ValueError("Invalid response format: 'content' not found in response.")
+        if not hasattr(response.choices[0], 'finish_reason'):
+            raise ValueError("Invalid response format: 'finish_reason' not found in response.")
         return GenerationResponse(
             text=response.choices[0].message.content,
             finish_reason=(

179-185: 🛠️ Refactor suggestion

Handle missing keys in from_json to prevent KeyError

The from_json method accesses keys directly from the input dictionary without checking if they exist. If any keys are missing, a KeyError will occur. Consider using dict.get() with default values or adding error handling.

Apply this diff to handle missing keys safely:

     @staticmethod
     def from_json(json: dict) -> "GenerativeModel":
+        model_name = json.get("model_name")
+        generation_config_json = json.get("generation_config")
+        system_instruction = json.get("system_instruction")
+
+        if model_name is None or generation_config_json is None:
+            raise ValueError("Invalid input: 'model_name' and 'generation_config' are required.")
+
         return LiteModel(
-            json["model_name"],
-            generation_config=GenerativeModelConfig.from_json(
-                json["generation_config"]
-            ),
-            system_instruction=json["system_instruction"],
+            model_name,
+            generation_config=GenerativeModelConfig.from_json(generation_config_json),
+            system_instruction=system_instruction,
         )

238-239: 🛠️ Refactor suggestion

Improve exception handling in send_message method

When re-raising an exception after catching it, include the original exception using from e to preserve the traceback. This helps in debugging by providing the original error context.

Apply this diff to enhance exception handling:

             except Exception as e:
-                raise ValueError(f"Error during completion request, please check the credentials - {e}")
+                raise ValueError(
+                    "Error during completion request. Please verify:\n"
+                    "1. Your credentials are correct.\n"
+                    "2. The model name is valid.\n"
+                    "3. The API endpoint is accessible."
+                ) from e
🧰 Tools
🪛 Ruff (0.8.2)

238-238: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🧹 Nitpick comments (3)
graphrag_sdk/models/litellm.py (1)

1-1: Remove unused import 'os'

The os module is imported but not used anywhere in the code. Removing it will clean up the code.

Apply this diff to remove the unused import:

-import os
🧰 Tools
🪛 Ruff (0.8.2)

1-1: os imported but unused

Remove unused import: os

(F401)

tests/test_kg_litellm_openai.py (2)

4-4: Remove unused import 'FalkorDB'

The import of FalkorDB is not used in this test file. Removing it will clean up the code.

Apply this diff to remove the unused import:

 import re
 import logging
 import unittest
-from falkordb import FalkorDB
 from dotenv import load_dotenv
🧰 Tools
🪛 Ruff (0.8.2)

4-4: falkordb.FalkorDB imported but unused

Remove unused import: falkordb.FalkorDB

(F401)


14-17: Avoid configuring logging at the module level in tests

Configuring logging globally using logging.basicConfig at the module level can affect other tests and modules. Consider configuring logging within the test class or methods to prevent unintended side effects.

Apply this diff to configure logging within the test class:

-load_dotenv()

-logging.basicConfig(level=logging.DEBUG)
 logger = logging.getLogger(__name__)

 class TestKGLiteLLM(unittest.TestCase):
     """
     Test Knowledge Graph
     """
+    @classmethod
+    def setUpClass(cls):
+        load_dotenv()
+        logging.basicConfig(level=logging.DEBUG)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24c1113 and a3b99b6.

📒 Files selected for processing (4)
  • .env.template (1 hunks)
  • graphrag_sdk/models/litellm.py (1 hunks)
  • graphrag_sdk/steps/create_ontology_step.py (0 hunks)
  • tests/test_kg_litellm_openai.py (1 hunks)
💤 Files with no reviewable changes (1)
  • graphrag_sdk/steps/create_ontology_step.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .env.template
🧰 Additional context used
🪛 Ruff (0.8.2)
graphrag_sdk/models/litellm.py

1-1: os imported but unused

Remove unused import: os

(F401)


78-78: Do not use bare except

(E722)


238-238: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

tests/test_kg_litellm_openai.py

4-4: falkordb.FalkorDB imported but unused

Remove unused import: falkordb.FalkorDB

(F401)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
graphrag_sdk/models/litellm.py (2)

181-186: 🛠️ Refactor suggestion

Handle Missing Keys in from_json to Prevent KeyError

The from_json method directly accesses keys from the input dictionary without checking if they exist. This could lead to a KeyError if any of the expected keys are missing. Consider using dict.get() with default values or adding explicit error handling to make the method more robust.

Apply this diff to handle missing keys:

     @staticmethod
     def from_json(json: dict) -> "GenerativeModel":
+        model_name = json.get("model_name")
+        generation_config_json = json.get("generation_config")
+        system_instruction = json.get("system_instruction")
+
+        if model_name is None or generation_config_json is None:
+            raise ValueError("Invalid input: 'model_name' and 'generation_config' are required.")
+
         return LiteModel(
-            json["model_name"],
+            model_name,
             generation_config=GenerativeModelConfig.from_json(
-                json["generation_config"]
+                generation_config_json
             ),
-            system_instruction=json["system_instruction"],
+            system_instruction=system_instruction,
         )

143-154: 🛠️ Refactor suggestion

Add Error Handling for Unexpected Response Structure

The parse_generate_content_response method assumes that response.choices[0].message.content and response.choices[0].finish_reason are always present. If the response is malformed or if choices is empty, this could raise an IndexError or AttributeError. Consider adding checks to ensure these fields are present before accessing them.

Apply this diff to add error handling:

     def parse_generate_content_response(self, response: any) -> GenerationResponse:
+        if not response.choices or not hasattr(response.choices[0], 'message') or not hasattr(response.choices[0].message, 'content'):
+            raise ValueError("Invalid response format: 'content' not found in response.")
+        if not hasattr(response.choices[0], 'finish_reason'):
+            raise ValueError("Invalid response format: 'finish_reason' not found in response.")
         return GenerationResponse(
             text=response.choices[0].message.content,
             finish_reason=(
🧹 Nitpick comments (1)
graphrag_sdk/models/litellm.py (1)

1-1: Remove unused import 'os'

The import of os at line 1 is unused in the code and should be removed to keep the codebase clean.

Apply this diff to remove the unused import:

-import os
🧰 Tools
🪛 Ruff (0.8.2)

1-1: os imported but unused

Remove unused import: os

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3b99b6 and f2e2574.

📒 Files selected for processing (2)
  • .env.template (1 hunks)
  • graphrag_sdk/models/litellm.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .env.template
🧰 Additional context used
🪛 Ruff (0.8.2)
graphrag_sdk/models/litellm.py

1-1: os imported but unused

Remove unused import: os

(F401)


78-78: Do not use bare except

(E722)


106-106: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


239-239: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (1)
graphrag_sdk/models/litellm.py (1)

78-79: 🛠️ Refactor suggestion

Specify Exception Type in 'except' Block

Using a bare except clause can catch unexpected exceptions and make debugging difficult. Specify the exception type to catch, such as Exception.

Apply this diff to update the except clause:

         try:
             completion(
                 model=model, messages=messages, max_tokens=10
             )
             return True
-        except:
+        except Exception:
             return False

Likely invalid or redundant comment.

🧰 Tools
🪛 Ruff (0.8.2)

78-78: Do not use bare except

(E722)

@galshubeli galshubeli merged commit e507bea into main Dec 16, 2024
3 checks passed
@galshubeli galshubeli deleted the litellm-int branch December 16, 2024 15:47
@coderabbitai coderabbitai bot mentioned this pull request Aug 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants