Skip to content

Staging#241

Merged
galshubeli merged 25 commits intomainfrom
staging
Sep 10, 2025
Merged

Staging#241
galshubeli merged 25 commits intomainfrom
staging

Conversation

@galshubeli
Copy link
Copy Markdown
Collaborator

@galshubeli galshubeli commented Sep 10, 2025

Summary by CodeRabbit

  • Improvements

    • Memory now builds history-aware, database-focused contexts and updates run concurrently.
    • Input state and placeholders update based on database selection; message input starts disabled until a DB is chosen.
    • Disabled delete controls visually hidden until hovered for clarity.
  • New Features

    • Demo databases flagged in the UI; refresh/delete actions are disabled for demos.
  • Bug Fixes

    • Query results emitted only when data exists.
    • Prevents fabricating or using email-based identifiers and avoids generating SQL when required personal info is missing.
  • UX

    • Removed follow-up apology/help message on invalid final results.

@galshubeli galshubeli requested a review from Copilot September 10, 2025 06:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Sep 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds stricter prompt constraints and personalization non‑translatability rules in the analysis agent; refactors MemoryTool to be history-aware with new/changed methods and direct Cypher node creation; routes and frontend gate demo graphs, pass history into memory persistence, and centralize chat input state.

Changes

Cohort / File(s) Summary
Analysis agent prompt & control flow
api/agents/analysis_agent.py
Adds a hard constraint forbidding email-based/encoded identifiers; consolidates prompt bullets; adds CRITICAL PERSONALIZATION CHECK that sets is_sql_translatable = false, appends missing_information, and leaves sql_query empty when personalization data is missing; reorganizes personalization guidance.
Memory tool: API, graph operations, concurrency
api/memory/graphiti_tool.py
Replaces legacy node-helper functions with direct Cypher entity creation (user node named by user_id); makes memory ops history-aware; adds update_user_information(...) and extract_episode_from_rel(...); updates signatures for add_new_memory(...), summarize_conversation(...), search_database_facts(...), and save_query_memory(...); composes composite database_context including episodes and runs updates concurrently.
Routes: streaming, demo gating, history-aware memory save
api/routes/graphs.py
Emits query_result event only when results exist; passes [queries_history, result_history] into memory_tool.add_new_memory(...); prevents refreshing demo graphs when graph_id starts with GENERAL_PREFIX.
Auth & template exposure of demo prefix
api/routes/auth.py, app/templates/base.j2
Adds module-level GENERAL_PREFIX from env and exposes it to templates; injects window.generalPrefix into base template script (JSON-encoded or null).
Frontend: input state and demo-aware graph selection
app/templates/components/chat_input.j2, app/ts/app.ts, app/ts/modules/chat.ts, app/ts/modules/graphs.ts, app/ts/modules/graph_select.ts
Chat textarea disabled by default with updated placeholder; new updateInputState() centralizes input/submit enabling and is exposed globally; removed follow-up message on invalid final results; graph listing/selection passes isDemo, sets data attribute, disables delete/refresh UI for demos and calls updateInputState().
CSS: disabled delete button styling
app/public/css/menu.css
Adds styles for disabled delete buttons inside dropdown options (muted color, not-allowed cursor, hidden until hover).
Config: Litellm logging suppression
api/config.py
Adds configure_litellm_logging() to disable/suppress LiteLLM logs on import and removes previous embedding import.
Minor UI wiring
app/templates/base.j2, app/ts/modules/graphs.ts, app/ts/modules/graph_select.ts
Exposes window.generalPrefix and wires demo detection into graph listing/selection flows (data attributes, disabled controls).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant FE as Frontend
  participant API as /graphs.generate
  participant AA as AnalysisAgent
  participant DB as SQL DB
  participant MT as MemoryTool

  U->>FE: enter question
  FE->>API: stream question + selected graph
  API->>AA: analyze(question, memory_context)
  AA-->>API: {is_sql_translatable, sql_query, missing_information}

  alt is_sql_translatable == true
    API->>DB: execute sql_query
    DB-->>API: query_results
    alt results non-empty
      API-->>FE: emit event: query_result
    end
    par Save memory concurrently
      API->>MT: add_new_memory(full_response, [queries_history, result_history])
      MT->>MT: summarize_conversation(..., history)
      MT->>MT: update_user_information(..., history)
      MT-->>API: memory saved
    and Stream final response
      API-->>FE: final response stream
    end
  else is_sql_translatable == false
    API-->>FE: explanation (missing_information) — no fabricated placeholders
    note right of FE: frontend no longer emits fallback follow-up on invalid final results
  end
Loading
sequenceDiagram
  autonumber
  participant MT as MemoryTool
  participant G as GraphDB
  participant LLM as LLM

  MT->>G: ensure entity nodes via Cypher (user node named by user_id)
  par Concurrent retrieval
    MT->>G: search_user_summary (direct Cypher)
    MT->>G: search_database_facts(query, episode_limit)
    MT->>G: search_similar_queries(query)
  end
  G-->>MT: user summary, facts, episodes, similar queries
  MT->>MT: assemble composite memory_context
  MT->>LLM: summarize_conversation(conversation, history)
  LLM-->>MT: {database_summary,...}
  MT->>LLM: update_user_information(conversation, history)
  LLM-->>MT: user summary update
  MT->>G: persist summaries, episodes, facts
  MT-->>Caller: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Add Memory component #150 — Overlapping memory subsystem changes and route callsite updates that pass history into MemoryTool.
  • Fix avoid lint #178 — Touches AnalysisAgent prompt/control-flow and memory/frontend integrations similar to this change.
  • Load DB in steps #161 — Related edits in api/routes/graphs.py affecting streaming and memory save call sites.

Suggested reviewers

  • gkorland
  • swilly22

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title “Staging” is overly generic and does not describe any of the substantive changes in this branch, such as the new prompt constraints in the AnalysisAgent, updates to memory graph methods, or UI state management improvements. As a result, teammates scanning history will not understand the intent or scope of these changes. A more specific title is needed to convey the primary focus of the changes. Please rename the pull request to a concise, descriptive title that highlights the main change—for example, “Enforce non-email identifiers and enhance personalization checks in AnalysisAgent” or “Rewrite personalization logic and update memory graph methods”—so reviewers can immediately grasp the key updates.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

Poem

I hop through nodes and tidy trails,
I guard the names — no emails in my tales.
I stitch the past when memory calls,
I block refresh where demo banner falls.
Small paws, bright maps — I keep the graph tall. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Sep 10, 2025

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link
Copy Markdown
Contributor

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.

Pull Request Overview

This PR implements memory-aware conversation handling in QueryWeaver, adding personalized context and user information management to improve query analysis and response generation.

Key changes:

  • Enhances memory management with user information updates and conversation history integration
  • Improves chat handling by removing error fallback messages for invalid SQL queries
  • Strengthens query analysis with better personal context validation and placeholder prevention

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
app/ts/modules/chat.ts Removes error message fallback when SQL query generation fails
api/routes/graphs.py Adds conversation history to memory save operations and conditional query result streaming
api/memory/graphiti_tool.py Major refactoring: adds user information updates, conversation history integration, and improved memory search
api/agents/analysis_agent.py Enhances personal query validation and prevents placeholder generation in SQL queries
Comments suppressed due to low confidence (1)

app/ts/modules/chat.ts:1

  • Removing the error message fallback when step.is_valid is false will leave users without feedback when SQL generation fails. This creates a poor user experience as users won't understand why their query didn't produce results. Consider replacing this with a more appropriate error handling mechanism or keeping some form of user feedback.
/**

Comment thread api/routes/graphs.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/agents/analysis_agent.py
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (3)
api/agents/analysis_agent.py (1)

244-244: Consider reformatting for consistency.

While the logic is correct, the line formatting could be more consistent with the surrounding bullet points.

-            - When there several tables that can be used to answer the question, you can combine them in a single SQL query.
+            - When there are several tables that can be used to answer the question, you can combine them in a single SQL query.
api/memory/graphiti_tool.py (2)

518-518: Consider suppressing or renaming unused loop variable.

The loop variable i is not used within the loop body.

-                for i, result in enumerate(reranked_results, 1):
+                for _i, result in enumerate(reranked_results, 1):

586-586: Consider improving readability of personal context label.

The current formatting might be clearer with standard parentheses formatting.

-                memory_context += f"(Personal preferences and information):\n{user_summary}\n\n"
+                memory_context += f"Personal preferences and information:\n{user_summary}\n\n"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2eb8f and e8aacf1.

📒 Files selected for processing (4)
  • api/agents/analysis_agent.py (3 hunks)
  • api/memory/graphiti_tool.py (11 hunks)
  • api/routes/graphs.py (2 hunks)
  • app/ts/modules/chat.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • app/ts/modules/chat.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/agents/analysis_agent.py
  • api/routes/graphs.py
  • api/memory/graphiti_tool.py
🧬 Code graph analysis (2)
api/routes/graphs.py (2)
app/ts/modules/config.ts (1)
  • MESSAGE_DELIMITER (5-5)
api/memory/graphiti_tool.py (1)
  • add_new_memory (251-279)
api/memory/graphiti_tool.py (1)
api/config.py (1)
  • Config (49-135)
🪛 Ruff (0.12.2)
api/memory/graphiti_tool.py

102-102: f-string without any placeholders

Remove extraneous f prefix

(F541)


214-214: String contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF001)


247-247: Consider moving this statement to an else block

(TRY300)


248-248: Do not catch blind exception: Exception

(BLE001)


248-248: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


456-456: Consider moving this statement to an else block

(TRY300)


458-458: Do not catch blind exception: Exception

(BLE001)


518-518: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


541-541: Consider moving this statement to an else block

(TRY300)


681-681: String contains ambiguous (EN DASH). Did you mean - (HYPHEN-MINUS)?

(RUF001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (12)
api/agents/analysis_agent.py (3)

240-240: LGTM! Good security practice for email privacy.

The addition of the constraint to never use email-based identifiers is a solid security improvement that helps protect user privacy.


257-262: Good implementation of personal context handling.

The logic correctly prevents placeholder fabrication when personal context is missing, ensuring queries fail gracefully rather than generating incorrect SQL with made-up placeholders.


290-300: LGTM! Well-structured evaluation steps.

The renumbered evaluation guidelines provide clear progression through the analysis process, particularly with the addition of memory context utilization and the critical personalization check.

api/memory/graphiti_tool.py (7)

85-85: Simplified naming convention looks good.

The change from User {user_id} to just {user_id} simplifies the node naming convention while maintaining uniqueness.


184-249: Well-designed user information update method.

The implementation properly handles existing summaries, integrates new insights, and maintains a factual third-person style. The error handling with the return of boolean status is appropriate.


251-279: Excellent concurrent processing implementation.

The refactored add_new_memory method efficiently runs database episode addition and user information update concurrently using asyncio.gather, improving performance while maintaining proper error handling.


445-460: Efficient direct database query for user summary.

Good refactoring to use direct Cypher queries instead of going through Graphiti search, which should improve performance for fetching user summaries.


462-486: Well-implemented episode extraction method.

The method properly extracts episode contents from relationship results, handling multiple episodes efficiently with proper null checking.


488-545: Good combination of episodes and facts in database context.

The method effectively combines recent episode contents with database facts to provide comprehensive context. The episode limit prevents overwhelming the context while still providing relevant history.


647-718: Well-structured conversation summarization with history support.

The method effectively uses conversation history to provide context for summarization, with proper handling of both cases (with and without history). The database-oriented prompting ensures relevant summaries.

api/routes/graphs.py (2)

496-503: Good conditional emission of query results.

The change to only emit query results when they're non-empty prevents unnecessary empty payloads in the response stream.


643-646: History parameters correctly passed queries_history and result_history are defined earlier (lines 286–303, 690) and correctly passed to memory_tool.add_new_memory.

Comment thread api/memory/graphiti_tool.py Outdated
Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/memory/graphiti_tool.py (1)

319-355: Use parameterized Cypher to avoid injection and escaping pitfalls.

Building Cypher with string interpolation (escaped_query, escaped_sql, escaped_error) risks injection and brittle escaping. Prefer parameters for all values (relationship type can remain interpolated).

Apply:

-            check_query = f"""
-            MATCH (db:Entity {{uuid: "{database_node_uuid}"}})
-            MATCH (db)-[r]->(q:Query)
-            WHERE q.user_query = "{escaped_query}" AND q.sql_query = "{escaped_sql}"
-            RETURN q.uuid as existing_query_uuid
-            LIMIT 1
-            """
-            check_result = await graph_driver.execute_query(check_query)
+            check_query = """
+            MATCH (db:Entity {uuid: $db_uuid})-[:SUCCESS|:FAILED]->(q:Query)
+            WHERE q.user_query = $user_query AND q.sql_query = $sql_query
+            RETURN q.uuid as existing_query_uuid
+            LIMIT 1
+            """
+            check_result, _, _ = await graph_driver.execute_query(
+                check_query,
+                db_uuid=database_node_uuid,
+                user_query=query,
+                sql_query=sql_query,
+            )
@@
-            cypher_query = f"""
-            MATCH (db:Entity {{uuid: "{database_node_uuid}"}})
-            MERGE (q:Query {{
-                user_query: "{escaped_query}",
-                sql_query: "{escaped_sql}",
-                success: {str(success).lower()},
-                error: "{escaped_error}",
-                timestamp: timestamp(),
-                embeddings: vecf32($embedding)
-            }})
-            CREATE (db)-[:{relationship_type} {{timestamp: timestamp()}}]->(q)
-            RETURN q.uuid as query_uuid
-            """
+            cypher_query = f"""
+            MATCH (db:Entity {{uuid: $db_uuid}})
+            MERGE (q:Query {{ user_query: $user_query, sql_query: $sql_query }})
+            ON CREATE SET q.success = $success,
+                          q.error = $error,
+                          q.timestamp = timestamp(),
+                          q.embeddings = vecf32($embedding)
+            ON MATCH SET  q.success = $success,
+                          q.error = $error,
+                          q.timestamp = timestamp()
+            MERGE (db)-[:{relationship_type} {{timestamp: timestamp()}}]->(q)
+            RETURN q.uuid as query_uuid
+            """
@@
-                result = await graph_driver.execute_query(cypher_query, embedding=embeddings)
+                _ = await graph_driver.execute_query(
+                    cypher_query,
+                    db_uuid=database_node_uuid,
+                    user_query=query,
+                    sql_query=sql_query,
+                    success=success,
+                    error=error or "",
+                    embedding=embeddings,
+                )
api/agents/analysis_agent.py (1)

266-283: f-string contains literal JSON braces → runtime formatting error.

The JSON template inside the f-string needs doubled braces to render literally. As written, Python will try to format them and crash.

-            ```json
-            {{
-                "is_sql_translatable": true or false,
-                "instructions_comments": ("Comments about any part of the instructions, "
-                                         "especially if they are unclear, impossible, "
-                                         "or partially met"),
-                "explanation": ("Detailed explanation why the query can or cannot be "
-                               "translated, mentioning instructions explicitly and "
-                               "referencing conversation history if relevant"),
-                "sql_query": ("High-level SQL query (you must to applying instructions "
-                             "and use previous answers if the question is a continuation)"),
-                "tables_used": ["list", "of", "tables", "used", "in", "the", "query",
-                               "with", "the", "relationships", "between", "them"],
-                "missing_information": ["list", "of", "missing", "information"],
-                "ambiguities": ["list", "of", "ambiguities"],
-                "confidence": integer between 0 and 100
-            }}
-            ```
+            ```json
+            {{
+                "is_sql_translatable": true or false,
+                "instructions_comments": ("Comments about any part of the instructions, "
+                                          "especially if they are unclear, impossible, "
+                                          "or partially met"),
+                "explanation": ("Detailed explanation why the query can or cannot be "
+                                "translated, mentioning instructions explicitly and "
+                                "referencing conversation history if relevant"),
+                "sql_query": ("High-level SQL query (you must to applying instructions "
+                              "and use previous answers if the question is a continuation)"),
+                "tables_used": ["list", "of", "tables", "used", "in", "the", "query",
+                                "with", "the", "relationships", "between", "them"],
+                "missing_information": ["list", "of", "missing", "information"],
+                "ambiguities": ["list", "of", "ambiguities"],
+                "confidence": integer between 0 and 100
+            }}
+            ```

Alternatively, build the JSON example as a separate non-f string and concatenate.

♻️ Duplicate comments (2)
api/memory/graphiti_tool.py (1)

85-85: Inconsistent user node naming ("User {id}" vs "{id}") will break lookups.

Entity nodes use name=f"{user_id}" (Line 85), but clean_memory pins "User {self.user_id}" (Lines 638–639). This inconsistency can cause accidental deletion of the user node.

Apply:

-                pinned_user=f"User {self.user_id}",
+                pinned_user=self.user_id,
api/routes/graphs.py (1)

496-504: Prefer truthiness check for results.

Use if query_results: instead of len(...) != 0. More Pythonic and handles None safely. This mirrors a prior suggestion.

-                        if len(query_results) != 0:
+                        if query_results:
🧹 Nitpick comments (8)
api/memory/graphiti_tool.py (7)

404-417: Make vector top-k configurable and consistent with limit.

Hardcoding 10 in db.idx.vector.queryNodes may return fewer-than-ideal candidates when limit > 10. Use top_k tied to limit.

Apply:

-                    CALL db.idx.vector.queryNodes('Query', 'embeddings', 10, vecf32($embedding))
+                    CALL db.idx.vector.queryNodes('Query', 'embeddings', {max(10, limit*2)}, vecf32($embedding))

Alternatively, compute top_k before the f-string and interpolate the integer.


472-487: Batch episode fetches to reduce N+1 queries.

extract_episode_from_rel() queries each episode UUID separately. Prefer a single MATCH ... WHERE e.uuid IN $uuids.

Example:

uuids = list(rel_result.episodes or [])
if not uuids:
    return []
q = "MATCH (e:Episodic) WHERE e.uuid IN $uuids RETURN e.content AS content"
records, _, _ = await driver.execute_query(q, uuids=uuids)
return [r["content"] for r in records if r.get("content")]

70-74: Index creation may fail on re-runs; make it idempotent.

CREATE VECTOR INDEX with a fixed label/property can error if it already exists. Guard with try/except or check existence first.

Would you like a helper that probes index existence and creates it only if missing?


102-103: Remove unnecessary f-string.

f"The User is using QueryWeaver" triggers Ruff F541. Drop the f prefix.

-                    'summary': f'The User is using QueryWeaver',
+                    'summary': 'The User is using QueryWeaver',

117-119: Use logging instead of print for server code.

print statements hinder observability and linting. Replace with logging at appropriate levels.

I can provide a small wrapper or direct diffs if you prefer.

Also applies to: 154-156, 173-176, 275-277, 361-362, 426-427, 544-545


582-616: Docstring vs return type mismatch.

search_memories() docstring says it returns a Dict, but it returns a string memory_context.

Update the docstring to match the actual return type.


673-682: Normalize Unicode punctuation in prompts.

“2–6 sentences” uses an en dash; prefer ASCII hyphen for linting (RUF001) and consistency.

-                - Keep it concise (2–6 sentences).  
+                - Keep it concise (2-6 sentences).  
api/agents/analysis_agent.py (1)

245-245: Grammar nit: “there” → “there are”.

-            - When there several tables that can be used to answer the question, you can combine them in a single SQL query.
+            - When there are several tables that can be used to answer the question, you can combine them in a single SQL query.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2eb8f and e6efea8.

📒 Files selected for processing (4)
  • api/agents/analysis_agent.py (3 hunks)
  • api/memory/graphiti_tool.py (11 hunks)
  • api/routes/graphs.py (2 hunks)
  • app/ts/modules/chat.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • app/ts/modules/chat.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/routes/graphs.py
  • api/agents/analysis_agent.py
  • api/memory/graphiti_tool.py
🧬 Code graph analysis (2)
api/routes/graphs.py (2)
app/ts/modules/config.ts (1)
  • MESSAGE_DELIMITER (5-5)
api/memory/graphiti_tool.py (1)
  • add_new_memory (251-279)
api/memory/graphiti_tool.py (1)
api/config.py (1)
  • Config (49-135)
🪛 Ruff (0.12.2)
api/memory/graphiti_tool.py

102-102: f-string without any placeholders

Remove extraneous f prefix

(F541)


214-214: String contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF001)


247-247: Consider moving this statement to an else block

(TRY300)


248-248: Do not catch blind exception: Exception

(BLE001)


248-248: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


456-456: Consider moving this statement to an else block

(TRY300)


458-458: Do not catch blind exception: Exception

(BLE001)


518-518: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


541-541: Consider moving this statement to an else block

(TRY300)


681-681: String contains ambiguous (EN DASH). Did you mean - (HYPHEN-MINUS)?

(RUF001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (1)
api/memory/graphiti_tool.py (1)

488-512: Verify correct Graphiti search API (search vs search_).

Elsewhere you use search_ (retrieve_similar_queries), here you call search. Confirm the intended method and align usage to avoid runtime AttributeError.

Would you like me to scan the repo for Graphiti client usages to standardize the method name?

Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/routes/graphs.py
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (2)
app/templates/components/chat_input.j2 (1)

4-4: Remove invalid textarea attribute; consider a11y hint.

type="text" is invalid on <textarea> and should be dropped. Optional: add aria-disabled="true" when disabled for clearer AT signaling.

-        <textarea type="text" id="message-input" placeholder="Please select a database first..." rows="1" disabled></textarea>
+        <textarea id="message-input" placeholder="Please select a database first..." rows="1" disabled></textarea>

Optional:

-        <textarea id="message-input" placeholder="Please select a database first..." rows="1" disabled></textarea>
+        <textarea id="message-input" placeholder="Please select a database first..." rows="1" disabled aria-disabled="true"></textarea>
app/ts/app.ts (1)

267-269: Avoid any on window with a global type.

Declare updateInputState (and related globals) to drop any casts.

Create app/ts/global.d.ts:

export {};

declare global {
  interface Window {
    updateInputState?: () => void;
    isAuthenticated?: boolean;
    currentGraph?: string;
    toggleSidebar?: (containerId: string) => void;
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6efea8 and f244e1a.

📒 Files selected for processing (3)
  • app/templates/components/chat_input.j2 (1 hunks)
  • app/ts/app.ts (3 hunks)
  • app/ts/modules/graphs.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend source changes in app/**/*.{ts,tsx} must be compiled before production runs

Files:

  • app/ts/modules/graphs.ts
  • app/ts/app.ts
🧬 Code graph analysis (1)
app/ts/app.ts (2)
app/ts/modules/config.ts (1)
  • DOM (39-65)
app/ts/modules/graph_select.ts (1)
  • getSelectedGraph (7-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (4)
app/ts/app.ts (2)

97-99: LGTM on centralizing input handling.

Replacing inline logic with updateInputState() keeps behavior consistent across modules.


264-265: Confirm auth flag is set before updateInputState runs.
We didn’t find any assignment to window.isAuthenticated in the TS code before DOMContentLoadedinitializeApploadInitialDataupdateInputState (app/ts/app.ts:264), so ensure the flag is injected or initialized (e.g. via inline script in your HTML) before this point.

app/ts/modules/graphs.ts (2)

90-92: LGTM: refresh input state after selection.

The guarded global call avoids runtime errors if the function isn’t present.


222-224: LGTM: re-sync input state on graph changes.

Keeps the input enablement/placeholder aligned with current selection.

Comment thread app/ts/app.ts
Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (3)
app/ts/app.ts (3)

271-274: Avoid overriding unauthenticated placeholder on load

Only run updateInputState() after graphs load if authenticated, otherwise you’ll clobber the “Please log in…” message set elsewhere.

 function loadInitialData() {
   loadGraphs();
-  updateInputState(); // Set initial input state based on database selection
+  if ((window as any).isAuthenticated) {
+    updateInputState(); // Set initial input state based on database selection
+  }
 }

90-95: Honor disabled state on Enter-to-send

Early-return when the submit button is disabled or the input is empty, so Enter respects UI state.

// replace handler body outside this range
if ((e as KeyboardEvent).key === "Enter" && !e.shiftKey) {
  e.preventDefault();
  if (DOM.submitButton?.disabled) return;
  if (!DOM.messageInput?.value.trim()) return;
  sendMessage();
}

56-78: Gate by auth and coerce boolean; avoid placeholder regression

updateInputState() overwrites the unauthenticated placeholder and treats selection loosely. Respect window.isAuthenticated first and coerce selection to boolean.

-function updateInputState() {
+function updateInputState() {
   const submitButton = DOM.submitButton;
   const messageInput = DOM.messageInput;
   if (!submitButton || !messageInput) return;
-  
-  const selected = getSelectedGraph();
-  const isDatabaseSelected = selected && selected !== "Select Database";
-  
-  if (isDatabaseSelected) {
+
+  const isAuthenticated =
+    (window as any).isAuthenticated !== undefined
+      ? !!(window as any).isAuthenticated
+      : false;
+  if (!isAuthenticated) {
+    messageInput.disabled = true;
+    messageInput.placeholder = "Please log in to start chatting";
+    submitButton.disabled = true;
+    return;
+  }
+
+  const selected = getSelectedGraph();
+  const isDatabaseSelected = !!selected && selected !== "Select Database";
+
+  if (isDatabaseSelected) {
     messageInput.disabled = false;
     messageInput.placeholder = "Describe the SQL query you want...";
     // Enable submit button only if there's also text in the input
-    if (messageInput.value.trim()) {
-      submitButton.disabled = false;
-    } else {
-      submitButton.disabled = true;
-    }
+    submitButton.disabled = messageInput.value.trim().length === 0;
   } else {
     messageInput.disabled = true;
     messageInput.placeholder = "Please select a database first...";
     submitButton.disabled = true;
   }
 }
🧹 Nitpick comments (5)
app/public/css/menu.css (1)

526-534: Keep disabled delete controls visually disabled on hover

Currently disabled delete buttons become fully visible on hover, which can mislead users into thinking they’re actionable. Keep a reduced opacity to signal disabled state while preserving the tooltip/title behavior.

-.dropdown-option:hover .delete-btn:disabled {
-  opacity: 1;
-}
+.dropdown-option:hover .delete-btn:disabled {
+  opacity: 0.5; /* remain visibly disabled on hover */
+}
api/routes/auth.py (1)

26-27: Clarify config source and avoid future drift

The comment suggests importing from “graphs route”, but the value is read from env here. Also, to avoid None propagation and ease template handling, default to an empty string. Consider centralizing all runtime config (e.g., api/config.py) so both auth and graphs import the same source.

-# Import GENERAL_PREFIX from graphs route
-GENERAL_PREFIX = os.getenv("GENERAL_PREFIX")
+# Demo graphs prefix (exposed to client)
+GENERAL_PREFIX = os.getenv("GENERAL_PREFIX") or ""
app/ts/modules/graph_select.ts (2)

42-44: Avoid innerHTML for SVG icon creation

Even though the string is static, linters flag innerHTML. Build the SVG with DOM APIs to eliminate the warning and reduce XSS surface.

-  delBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path></svg>`;
+  delBtn.appendChild(createTrashIcon());

Outside this range, add:

function createTrashIcon(): SVGElement {
  const ns = "http://www.w3.org/2000/svg";
  const svg = document.createElementNS(ns, "svg");
  svg.setAttribute("viewBox", "0 0 24 24");
  svg.setAttribute("fill", "none");
  svg.setAttribute("stroke", "currentColor");
  svg.setAttribute("stroke-width", "2");
  svg.setAttribute("stroke-linecap", "round");
  svg.setAttribute("stroke-linejoin", "round");
  const paths = [
    { tag: "polyline", attrs: { points: "3 6 5 6 21 6" } },
    { tag: "path", attrs: { d: "M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" } },
    { tag: "path", attrs: { d: "M10 11v6" } },
    { tag: "path", attrs: { d: "M14 11v6" } },
    { tag: "path", attrs: { d: "M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" } },
  ];
  for (const p of paths) {
    const el = document.createElementNS(ns, p.tag);
    for (const [k, v] of Object.entries(p.attrs)) el.setAttribute(k, v as string);
    svg.appendChild(el);
  }
  return svg;
}

52-57: Don’t force-enable submit here; defer to centralized state management

Directly setting submitButton.disabled = false on option click can conflict with the app-level updateInputState() (auth, text presence). Let the central logic decide.

-    if (DOM.graphSelectRefresh && DOM.submitButton) {
-        // Disable refresh button for demo databases
-        DOM.graphSelectRefresh.disabled = isDemo;
-        DOM.submitButton.disabled = false
-    };
+    if (DOM.graphSelectRefresh) {
+        // Disable refresh button for demo databases
+        DOM.graphSelectRefresh.disabled = isDemo;
+    }

Optionally trigger the global handler (outside changed range):

(window as any).updateInputState?.();
app/ts/app.ts (1)

123-131: Coerce isDemo to boolean for clarity

const isDemo = generalPrefix && selected.startsWith(generalPrefix); yields string|false. Coerce to boolean to avoid accidental truthy string usage.

-    const generalPrefix = (window as any).generalPrefix;
-    const isDemo = generalPrefix && selected.startsWith(generalPrefix);
+    const generalPrefix = (window as any).generalPrefix as string | null;
+    const isDemo = !!generalPrefix && selected.startsWith(generalPrefix);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f244e1a and 175a137.

📒 Files selected for processing (7)
  • api/routes/auth.py (2 hunks)
  • api/routes/graphs.py (3 hunks)
  • app/public/css/menu.css (1 hunks)
  • app/templates/base.j2 (1 hunks)
  • app/ts/app.ts (4 hunks)
  • app/ts/modules/graph_select.ts (2 hunks)
  • app/ts/modules/graphs.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/routes/graphs.py
  • app/ts/modules/graphs.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/routes/auth.py
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend source changes in app/**/*.{ts,tsx} must be compiled before production runs

Files:

  • app/ts/app.ts
  • app/ts/modules/graph_select.ts
🧬 Code graph analysis (2)
app/ts/app.ts (2)
app/ts/modules/config.ts (1)
  • DOM (39-65)
app/ts/modules/graph_select.ts (1)
  • getSelectedGraph (7-12)
app/ts/modules/graph_select.ts (1)
app/ts/modules/config.ts (1)
  • DOM (39-65)
🪛 ast-grep (0.38.6)
app/ts/modules/graph_select.ts

[warning] 42-42: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: delBtn.innerHTML = <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path></svg>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)


[warning] 42-42: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: delBtn.innerHTML = <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"></path><path d="M10 11v6"></path><path d="M14 11v6"></path><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path></svg>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (5)
api/routes/auth.py (1)

409-411: LGTM: Exposes general_prefix to templates

Passing GENERAL_PREFIX through the template context is correct and matches the frontend expectation (window.generalPrefix).

app/templates/base.j2 (1)

44-44: LGTM: Safe serialization for global

Using tojson to emit window.generalPrefix is correct and safe; falls back to null when unset.

app/ts/modules/graph_select.ts (1)

25-32: Backwards-compatible API extension

Adding isDemo: boolean = false and reflecting it via data-is-demo keeps existing callers working and enables per-row behavior.

app/ts/app.ts (2)

97-99: LGTM: Centralized input-state refresh on input events

Replacing ad-hoc logic with updateInputState() keeps behavior consistent.


276-278: LGTM: Exposing updateInputState globally

Makes it easy for modules like graphs.ts to sync UI on selection changes.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (2)
api/config.py (2)

12-20: Avoid globally disabling the logger; prefer level + no-propagation (and cover both logger names).

Disabling a library logger at import-time can mask important errors and surprise downstream apps. Setting ERROR level and stopping propagation keeps noise and PII down without silencing errors. Also, some versions may register the logger as "litellm" instead of "LiteLLM".

Apply:

-    # Disable LiteLLM logger that outputs
-    litellm_logger = logging.getLogger("LiteLLM")
-    litellm_logger.setLevel(logging.ERROR)
-    litellm_logger.disabled = True
+    # Reduce LiteLLM logs without silencing errors; support both logger names
+    for _name in ("LiteLLM", "litellm"):
+        _logger = logging.getLogger(_name)
+        _logger.setLevel(logging.ERROR)
+        _logger.propagate = False
+        # Optionally add a NullHandler to avoid "No handler" warnings
+        if not _logger.handlers:
+            _logger.addHandler(logging.NullHandler())

22-24: Avoid side effects on import; gate configuration behind an env flag.

Running configuration at import can surprise consumers and complicate tests. Gate it so apps can opt out easily.

-# Initialize litellm configuration
-configure_litellm_logging()
+# Initialize litellm configuration (opt-out via env)
+if os.getenv("SUPPRESS_LITELLM_LOGS", "1") == "1":
+    configure_litellm_logging()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 175a137 and dd2e519.

📒 Files selected for processing (1)
  • api/config.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/config.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: e2e-tests
  • GitHub Check: unit-tests
  • GitHub Check: e2e-tests
  • GitHub Check: Analyze (python)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: unit-tests
🔇 Additional comments (1)
api/config.py (1)

7-7: LGTM: import logging is appropriate for the new logger config.

Comment thread api/config.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Comment thread app/ts/modules/graph_select.ts
Comment thread api/memory/graphiti_tool.py Outdated
Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
api/memory/graphiti_tool.py (3)

306-359: Cypher injection and brittle string escaping in save_query_memory.

Build queries with parameters instead of interpolating escaped strings. Also destructure execute_query results consistently.

-            database_result = await graph_driver.execute_query(find_database_query, name=database_node_name)
+            records, __, _ = await graph_driver.execute_query(find_database_query, name=database_node_name)
@@
-            if not database_result[0]:  # If no records found
+            if not records:  # If no records found
                 print(f"Database entity node {database_node_name} not found")
                 return False
-            database_node_uuid = database_result[0][0]['uuid']
+            database_node_uuid = records[0].get('uuid', '')
@@
-            escaped_query = query.replace("'", "\\'").replace('"', '\\"')
-            escaped_sql = sql_query.replace("'", "\\'").replace('"', '\\"')
-            escaped_error = error.replace("'", "\\'").replace('"', '\\"') if error else ""
-            embeddings = Config.EMBEDDING_MODEL.embed(escaped_query)[0]
+            embeddings = Config.EMBEDDING_MODEL.embed(query)[0]
@@
-            check_query = f"""
-            MATCH (db:Entity {{uuid: "{database_node_uuid}"}})
-            MATCH (db)-[r]->(q:Query)
-            WHERE q.user_query = "{escaped_query}" AND q.sql_query = "{escaped_sql}"
-            RETURN q.uuid as existing_query_uuid
-            LIMIT 1
-            """
+            check_query = """
+            MATCH (db:Entity {uuid: $db_uuid})
+            MATCH (db)-[r]->(q:Query)
+            WHERE q.user_query = $user_query AND q.sql_query = $sql_query
+            RETURN q.uuid as existing_query_uuid
+            LIMIT 1
+            """
@@
-            check_result = await graph_driver.execute_query(check_query)
+            check_result, __, _ = await graph_driver.execute_query(
+                check_query,
+                db_uuid=database_node_uuid,
+                user_query=query,
+                sql_query=sql_query,
+            )
@@
-            cypher_query = f"""
-            MATCH (db:Entity {{uuid: "{database_node_uuid}"}})
-            MERGE (q:Query {{
-                user_query: "{escaped_query}",
-                sql_query: "{escaped_sql}",
-                success: {str(success).lower()},
-                error: "{escaped_error}",
-                timestamp: timestamp(),
-                embeddings: vecf32($embedding)
-            }})
-            CREATE (db)-[:{relationship_type} {{timestamp: timestamp()}}]->(q)
-            RETURN q.uuid as query_uuid
-            """
+            cypher_query = f"""
+            MATCH (db:Entity {{uuid: $db_uuid}})
+            MERGE (q:Query {{ user_query: $user_query, sql_query: $sql_query }})
+            SET q.success = $success,
+                q.error = $error,
+                q.timestamp = timestamp(),
+                q.embeddings = vecf32($embedding)
+            MERGE (db)-[:{relationship_type} {{timestamp: timestamp()}}]->(q)
+            RETURN q.uuid as query_uuid
+            """
@@
-                result = await graph_driver.execute_query(cypher_query, embedding=embeddings)
+                await graph_driver.execute_query(
+                    cypher_query,
+                    db_uuid=database_node_uuid,
+                    user_query=query,
+                    sql_query=sql_query,
+                    success=success,
+                    error=error or "",
+                    embedding=embeddings,
+                )
                 return True

500-511: Critical: function-arg “query” is shadowed by Cypher string in search_database_facts.

You pass the Cypher into graphiti_client.search() instead of the user’s search text.

-            driver = self.graphiti_client.driver
-            query = """
+            driver = self.graphiti_client.driver
+            center_node_cypher = """
                     MATCH (e:Entity {name: $name})
                     RETURN e.uuid AS uuid
                     """
-            result, __, _ = await driver.execute_query(query, name=f"Database {self.graph_id}")
-            center_node_uuid = result[0].get("uuid", "")
-            reranked_results = await self.graphiti_client.search(
-                query=query,
+            result, __, _ = await driver.execute_query(center_node_cypher, name=f"Database {self.graph_id}")
+            if not result:
+                return ""
+            center_node_uuid = result[0].get("uuid", "")
+            reranked_results = await self.graphiti_client.search(
+                query=query,  # preserve the user's input
                 center_node_uuid=center_node_uuid,
                 num_results=limit
             )
@@
-                for i, result in enumerate(reranked_results, 1):
-                    if result.source_node_uuid != center_node_uuid and result.target_node_uuid != center_node_uuid:
+                for rel in reranked_results:
+                    if rel.source_node_uuid != center_node_uuid and rel.target_node_uuid != center_node_uuid:
                         continue
                     if len(episodes_contents) < episode_limit:
-                        episodes_content = await self.extract_episode_from_rel(result)
+                        episodes_content = await self.extract_episode_from_rel(rel)
                         episodes_contents.extend(episodes_content)
-                    fact_entry = f"{result.fact}"
+                    fact_entry = f"{rel.fact}"

Also applies to: 516-525


647-706: Summarization: align history typing, normalize, and fix unicode dash.

  • Signature should accept Optional[Tuple[List[str], List[str]]] and normalize.
  • Replace en dash in prompt (RUF001).
  • Avoid “query” as loop var.
-    async def summarize_conversation(self, conversation: Dict[str, Any], history: List[Dict[str, Any]]) -> Dict[str, Any]:
+    async def summarize_conversation(
+        self,
+        conversation: Dict[str, Any],
+        history: Optional[Tuple[List[str], List[str]]] = None,
+    ) -> Dict[str, Any]:
@@
-                - Keep it concise (2–6 sentences).  
+                - Keep it concise (2-6 sentences).  
@@
-            if len(history[1]) == 0:
-                messages = [{"role": "user", "content": prompt}]
-            else:
-                messages = []
-                for query, result in zip(history[0], history[1]):
-                    messages.append({"role": "user", "content": query})
-                    messages.append({"role": "assistant", "content": result})
+            queries, results = (history or ([], []))
+            if len(results) == 0:
+                messages = [{"role": "user", "content": prompt}]
+            else:
+                messages = []
+                for q, r in zip(queries, results):
+                    messages.append({"role": "user", "content": q})
+                    messages.append({"role": "assistant", "content": r})
             messages.append({"role": "user", "content": prompt})
@@
-                messages=messages,
+                messages=messages,

Also applies to: 694-701

♻️ Duplicate comments (1)
api/memory/graphiti_tool.py (1)

638-639: Pinned user name mismatch can delete the user node.

Elsewhere you set user entity name to user_id, but clean_memory excludes "User {self.user_id}". Use self.user_id to avoid accidental deletion. (Previously raised.)

-                pinned_user=f"User {self.user_id}",
+                pinned_user=self.user_id,
🧹 Nitpick comments (7)
api/memory/graphiti_tool.py (7)

70-74: Make vector-index creation idempotent.

Creating the same index on every tool initialization can fail or race under concurrency. Wrap with IF NOT EXISTS (if supported) or catch the duplicate-index error.

Apply:

-        await driver.execute_query(f"CREATE VECTOR INDEX FOR (p:Query) ON (p.embeddings) OPTIONS {{dimension:{vector_size}, similarityFunction:'euclidean'}}")
+        try:
+            await driver.execute_query(
+                f"CREATE VECTOR INDEX FOR (p:Query) ON (p.embeddings) "
+                f"OPTIONS {{dimension:{vector_size}, similarityFunction:'euclidean'}}"
+            )
+        except Exception as e:
+            # Ignore if index already exists
+            if "already exists" not in str(e).lower():
+                raise

85-85: Drop unnecessary f-string.

No interpolation needed.

-            user_node_name = f"{user_id}"
+            user_node_name = user_id

445-451: Avoid “query” shadowing; name Cypher clearly.

This prevents confusion with other variables named query.

-            driver = self.graphiti_client.driver
-            query = """
+            driver = self.graphiti_client.driver
+            user_summary_cypher = """
                     MATCH (e:Entity {name: $name})
                     RETURN e.summary AS summary
                     """
-            result, __, _ = await driver.execute_query(query, name=self.user_id)
+            result, __, _ = await driver.execute_query(user_summary_cypher, name=self.user_id)

537-541: Minor flow nit: move join/return into an else.

Not required, but aligns with TRY300.

-            facts = "\n".join(database_facts_text) if database_facts_text else ""
-            episodes = "\n".join(episodes_contents) if episodes_contents else ""
-            database_context = "Previous sessions:\n" + episodes + "\n\nFacts:\n" + facts
-            # Join all facts into a single string
-            return database_context
+            facts = "\n".join(database_facts_text) if database_facts_text else ""
+            episodes = "\n".join(episodes_contents) if episodes_contents else ""
+            database_context = "Previous sessions:\n" + episodes + "\n\nFacts:\n" + facts
+            return database_context

586-589: Text label consistency (optional).

Consider dropping the parentheses around “Personal preferences and information” for consistency with other section headers.

-                memory_context += f"(Personal preferences and information):\n{user_summary}\n\n"
+                memory_context += f"PERSONAL PREFERENCES AND INFORMATION:\n{user_summary}\n\n"

100-101: Use plain underscore for group_id.

Storing the literal "_" may be unintended.

-                    'group_id': '\\_',
+                    'group_id': '_',

518-518: Unused enumerate index.

Index isn’t used; drop it to satisfy Ruff B007.

-                for i, result in enumerate(reranked_results, 1):
+                for result in reranked_results:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd2e519 and 4794048.

📒 Files selected for processing (1)
  • api/memory/graphiti_tool.py (11 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/memory/graphiti_tool.py
🧬 Code graph analysis (1)
api/memory/graphiti_tool.py (1)
api/config.py (1)
  • Config (62-148)
🪛 Ruff (0.12.2)
api/memory/graphiti_tool.py

214-214: String contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF001)


247-247: Consider moving this statement to an else block

(TRY300)


248-248: Do not catch blind exception: Exception

(BLE001)


248-248: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


456-456: Consider moving this statement to an else block

(TRY300)


458-458: Do not catch blind exception: Exception

(BLE001)


518-518: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


541-541: Consider moving this statement to an else block

(TRY300)


681-681: String contains ambiguous (EN DASH). Did you mean - (HYPHEN-MINUS)?

(RUF001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: Analyze (python)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (3)
api/memory/graphiti_tool.py (3)

102-102: LGTM: removed unnecessary f-string.

This fixes the Ruff F541 warning.


262-274: Concurrent episode + user update: good pattern.

Running add_episode and update_user_information concurrently is appropriate.


251-254: I’ve initiated a search for the summarize_conversation definition.

Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
@galshubeli galshubeli requested a review from Naseem77 September 10, 2025 11:09
Comment thread api/memory/graphiti_tool.py Dismissed
Comment thread api/memory/graphiti_tool.py Dismissed
Comment thread api/memory/graphiti_tool.py Fixed
Comment thread api/memory/graphiti_tool.py Fixed
Copy link
Copy Markdown
Contributor

@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: 0

♻️ Duplicate comments (3)
api/memory/graphiti_tool.py (3)

501-511: Critical variable shadowing issue in search_database_facts.

The local variable query shadows the function parameter, causing self.graphiti_client.search() to receive Cypher text instead of the user's search query.

This is consistent with the previous review comment about renaming the Cypher variable to avoid shadowing the function argument.


648-701: Critical type annotation and history handling issues.

The method has the same issues as update_user_information:

  1. Type mismatch: Annotated as List[Dict[str, Any]] but used as Tuple[List[str], List[str]]
  2. History handling: Assumes history[1] exists without null checks
  3. Variable reuse: query and result variables shadow loop parameters

This is consistent with previous review comments about unifying history typing and handling None safely.


463-487: N+1 query performance issue in extract_episode_from_rel.

The method executes a separate database query for each episode UUID, creating an N+1 query problem.

This is consistent with the previous review comment about batching episode fetches by UUIDs to reduce database round-trips.

🧹 Nitpick comments (1)
api/memory/graphiti_tool.py (1)

682-682: Fix Unicode character in prompt text.

The prompt contains an en dash (–) character that should be replaced with a standard hyphen-minus (-) for consistency.

-                - Keep it concise (2–6 sentences).  
+                - Keep it concise (2-6 sentences).  
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4794048 and 4b7993a.

📒 Files selected for processing (1)
  • api/memory/graphiti_tool.py (19 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/memory/graphiti_tool.py
🧬 Code graph analysis (1)
api/memory/graphiti_tool.py (1)
api/config.py (1)
  • Config (62-148)
🪛 GitHub Check: CodeQL
api/memory/graphiti_tool.py

[failure] 155-155: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.


[failure] 157-157: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.


[failure] 173-173: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.


[failure] 182-182: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.

🪛 Ruff (0.12.2)
api/memory/graphiti_tool.py

175-175: Do not catch blind exception: Exception

(BLE001)


176-176: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


179-179: Consider moving this statement to an else block

(TRY300)


181-181: Do not catch blind exception: Exception

(BLE001)


182-182: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


215-215: String contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF001)


248-248: Consider moving this statement to an else block

(TRY300)


249-249: Do not catch blind exception: Exception

(BLE001)


249-249: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


276-276: Do not catch blind exception: Exception

(BLE001)


277-277: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


362-362: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


365-365: Do not catch blind exception: Exception

(BLE001)


366-366: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


427-427: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


430-430: Do not catch blind exception: Exception

(BLE001)


431-431: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


457-457: Consider moving this statement to an else block

(TRY300)


459-459: Do not catch blind exception: Exception

(BLE001)


460-460: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


519-519: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


542-542: Consider moving this statement to an else block

(TRY300)


544-544: Do not catch blind exception: Exception

(BLE001)


545-545: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


620-620: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


645-645: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


682-682: String contains ambiguous (EN DASH). Did you mean - (HYPHEN-MINUS)?

(RUF001)


716-716: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (4)
api/memory/graphiti_tool.py (4)

447-461: LGTM! Simplified user summary retrieval.

The refactor to use direct Cypher queries instead of Graphiti search results improves performance and reliability for user summary retrieval.


516-542: LGTM! Enhanced database facts context structure.

The refactor to build comprehensive database context with "Previous sessions" and "Facts" sections provides better structure for the analysis agent.


587-587: LGTM! Improved memory context labeling.

The personal context is now clearly labeled as "(Personal preferences and information)" which helps the analysis agent understand the context type.


674-690: LGTM! Enhanced prompt structure for database-oriented summaries.

The updated prompt clearly specifies requirements for database-oriented conversational summaries and includes explicit formatting requirements.

Comment thread api/memory/graphiti_tool.py Dismissed
@galshubeli galshubeli merged commit f08092e into main Sep 10, 2025
16 of 17 checks passed
Copy link
Copy Markdown
Contributor

@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 (4)
api/memory/graphiti_tool.py (4)

488-546: Critical: Variable shadowing breaks search functionality.

The local variable query on Line 501 shadows the function parameter query, causing self.graphiti_client.search() to receive the Cypher string instead of the user's search query.

Apply this fix:

         try:
             driver = self.graphiti_client.driver
-            query = """
+            center_node_query = """
                     MATCH (e:Entity {name: $name})
                     RETURN e.uuid AS uuid
                     """
-            result, __, _ = await driver.execute_query(query, name=f"Database {self.graph_id}")
+            result, __, _ = await driver.execute_query(center_node_query, name=f"Database {self.graph_id}")
             center_node_uuid = result[0].get("uuid", "")
             reranked_results = await self.graphiti_client.search(
                 query=query,  # This now correctly uses the function parameter
                 center_node_uuid=center_node_uuid,
                 num_results=limit
             )

Also improve exception logging:

         except Exception as e:
-            logging.error("Error searching database facts for %s: %s", self.graph_id, e)
+            logging.exception("Error searching database facts for %s", self.graph_id)
             return ""

462-486: N+1 query issue in extract_episode_from_rel.

The method fetches episode content one UUID at a time, creating an N+1 query problem. This should be optimized with batch fetching.

Apply this fix to batch fetch all episodes:

     async def extract_episode_from_rel(self, rel_result):
         """
         Extracts the content of episodes associated with a given relationship result.

         Args:
             rel_result: An object containing an 'episodes' attribute, which is a list of episode UUIDs.

         Returns:
             List of episode content strings corresponding to the provided episode UUIDs.
         """
         driver = self.graphiti_client.driver
-        query = """
-                MATCH (e:Episodic {uuid: $uuid})
+        batch_query = """
+                MATCH (e:Episodic)
+                WHERE e.uuid IN $uuids
                 RETURN e.content AS content
                 """
         episodes_uuid = rel_result.episodes

         episode_contents = []
-        for episode_uuid in episodes_uuid:
-            episode_content, _, _ = await driver.execute_query(query, uuid=episode_uuid)
-            if episode_content:
-                content = episode_content[0].get("content")
-                episode_contents.append(content)
+        if episodes_uuid:
+            episode_results, _, _ = await driver.execute_query(batch_query, uuids=episodes_uuid)
+            episode_contents = [row.get("content") for row in episode_results if row.get("content")]

         return episode_contents

647-647: Critical: Type annotation inconsistency.

The method signature shows history: List[Dict[str, Any]] but the method uses it as Tuple[List[str], List[str]] (Lines 694-700), which will cause runtime errors.

Apply this fix:

-    async def summarize_conversation(self, conversation: Dict[str, Any], history: List[Dict[str, Any]]) -> Dict[str, Any]:
+    async def summarize_conversation(
+        self,
+        conversation: Dict[str, Any],
+        history: Optional[Tuple[List[str], List[str]]] = None,
+    ) -> Dict[str, Any]:

Also fix the history handling:

-            if len(history[1]) == 0:
+            queries, results = (history or ([], []))
+            if len(results) == 0:
                 messages = [{"role": "user", "content": prompt}]
             else:
                 messages = []
-                for query, result in zip(history[0], history[1]):
-                    messages.append({"role": "user", "content": query})
+                for q, result in zip(queries, results):
+                    messages.append({"role": "user", "content": q})
                     messages.append({"role": "assistant", "content": result})

184-250: Critical: Multiple issues in update_user_information method.

This method has several significant problems:

  1. Variable shadowing: query variable shadows the method parameter and is reused for different Cypher queries
  2. History type inconsistency: Method expects Tuple[List[str], List[str]] but calls like Line 270 may pass None
  3. Ambiguous apostrophe: Line 214 uses curly quote instead of ASCII apostrophe
  4. Exception handling: Catches Exception without logging details

Apply this comprehensive fix:

-    async def update_user_information(self, conversation: Dict[str, Any], history: Tuple[List[str], List[str]]) -> bool:
+    async def update_user_information(
+        self,
+        conversation: Dict[str, Any],
+        history: Optional[Tuple[List[str], List[str]]] = None,
+    ) -> bool:
         driver = self.graphiti_client.driver
-        query = """
+        get_summary_query = """
             MATCH (u:Entity {name: $user_id})
             RETURN u.summary AS summary
         """
-        summary_result, __, _ = await driver.execute_query(query, user_id=self.user_id)
+        summary_result, __, _ = await driver.execute_query(get_summary_query, user_id=self.user_id)
         summary = summary_result[0].get("summary", "") if summary_result else ""
         # Format conversation for summarization
         conv_text = ""
         conv_text += f"User: {conversation.get('question', '')}\n"
         if conversation.get('generated_sql'):
             conv_text += f"SQL: {conversation['generated_sql']}\n"
         if conversation.get('error'):
             conv_text += f"Error: {conversation['error']}\n"
         if conversation.get('answer'):
             conv_text += f"Assistant: {conversation['answer']}\n"
         prompt = f"""
                 You are updating the personal memory of user.
                 ### Inputs
                 1. Existing user summary (overall + personal info):
                 {summary}

                 2. Latest Q&A conversational memory:
                 {conv_text}

                 ### Task
-                - Produce a new user summary of his overall preferences and his personal information.
-                - *Important*: Ensure that the summary is contain any personal statements or preferences expressed by the user.
+                - Produce a new user summary of the user's overall preferences and personal information.
+                - *Important*: Ensure that the summary contains any personal statements or preferences expressed by the user.
                 - Preserve existing personal information, preferences, and tendencies from the old summary.
                 - Integrate any **new insights** about the user's interests, behaviors, or database usage patterns from the latest memory.
                 - If new info refines or corrects older info, update accordingly.
                 - Focus only on **overall and personal information** — do not include temporary query details.
                 - Write in **factual third-person style**, suitable for storage as a user node in a graph.
                 - Try to explicitly divide overall summary, usage preferences and personal information.

                 ### Output
                 An updated user summary.
                 """
         try:
-            if len(history[1]) == 0:
+            queries, results = (history or ([], []))
+            if len(results) == 0:
                 messages = [{"role": "user", "content": prompt}]
             else:
                 messages = []
-                for query, result in zip(history[0], history[1]):
-                    messages.append({"role": "user", "content": query})
+                for q, result in zip(queries, results):
+                    messages.append({"role": "user", "content": q})
                     messages.append({"role": "assistant", "content": result})
             messages.append({"role": "user", "content": prompt})
             response = completion(
                 model=Config.COMPLETION_MODEL,
                 messages=messages,
                 temperature=0.1
             )
             
             # Parse the direct text response (no JSON parsing needed)
             content = response.choices[0].message.content.strip()
-            query = """
+            update_query = """
             MATCH (u:Entity {name: $user_id})
             SET u.summary = $summary
             RETURN u.summary AS summary
             """
-            await driver.execute_query(query, user_id=self.user_id, summary=content)
+            await driver.execute_query(update_query, user_id=self.user_id, summary=content)
             return True
         except Exception as e:
+            logging.exception("Failed to update user information")
             return False
🧹 Nitpick comments (6)
api/memory/graphiti_tool.py (6)

445-461: LGTM! Simplified direct user summary retrieval.

The refactoring from using self.graphiti_client.search_ to direct Cypher queries simplifies the logic and should be more performant. The error handling is appropriate.

Minor: Improve exception logging.

         except Exception as e:
-            logging.error("Error searching user node: %s", e)
+            logging.exception("Error searching user node")
             return ""

517-518: Unused loop variable.

The loop variable i is not used within the loop body. This is flagged by static analysis.

-                for i, result in enumerate(reranked_results, 1):
+                for result in reranked_results:

619-620: Improve exception logging.

Use logging.exception() for better debugging context.

         except Exception as e:
-            logging.error("Error in concurrent memory search: %s", e)
+            logging.exception("Error in concurrent memory search")
             return ""

644-645: Improve exception logging.

Use logging.exception() for better debugging context.

         except Exception as e:
-            logging.error("Error cleaning memory: %s", e)
+            logging.exception("Error cleaning memory")
             return 0

681-681: Fix ambiguous character in prompt.

Line 681 uses an en-dash (–) instead of a standard hyphen (-).

-                - Keep it concise (2–6 sentences).  
+                - Keep it concise (2-6 sentences).  

715-716: Improve exception logging.

Use logging.exception() for better debugging context.

         except Exception as e:
-            logging.error("Error in LLM summarization: %s", e)
+            logging.exception("Error in LLM summarization")
             return {
                 "database_summary": ""
             }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b7993a and ec8f246.

📒 Files selected for processing (1)
  • api/memory/graphiti_tool.py (19 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

All Python code should pass pylint (use make lint)

Files:

  • api/memory/graphiti_tool.py
🧬 Code graph analysis (1)
api/memory/graphiti_tool.py (1)
api/config.py (1)
  • Config (62-148)
🪛 Ruff (0.12.2)
api/memory/graphiti_tool.py

174-174: Do not catch blind exception: Exception

(BLE001)


175-175: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


178-178: Consider moving this statement to an else block

(TRY300)


180-180: Do not catch blind exception: Exception

(BLE001)


181-181: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


214-214: String contains ambiguous (RIGHT SINGLE QUOTATION MARK). Did you mean ``` (GRAVE ACCENT)?

(RUF001)


247-247: Consider moving this statement to an else block

(TRY300)


248-248: Do not catch blind exception: Exception

(BLE001)


248-248: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


275-275: Do not catch blind exception: Exception

(BLE001)


276-276: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


361-361: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


364-364: Do not catch blind exception: Exception

(BLE001)


365-365: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


426-426: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


429-429: Do not catch blind exception: Exception

(BLE001)


430-430: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


456-456: Consider moving this statement to an else block

(TRY300)


458-458: Do not catch blind exception: Exception

(BLE001)


459-459: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


518-518: Loop control variable i not used within loop body

Rename unused i to _i

(B007)


541-541: Consider moving this statement to an else block

(TRY300)


543-543: Do not catch blind exception: Exception

(BLE001)


544-544: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


619-619: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


644-644: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


681-681: String contains ambiguous (EN DASH). Did you mean - (HYPHEN-MINUS)?

(RUF001)


715-715: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🪛 GitHub Check: CodeQL
api/memory/graphiti_tool.py

[failure] 173-173: Log Injection
This log entry depends on a user-provided value.
This log entry depends on a user-provided value.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: unit-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
  • GitHub Check: e2e-tests
🔇 Additional comments (4)
api/memory/graphiti_tool.py (4)

103-103: LGTM! Summary text updated appropriately.

The change from f'User {user_id} is using QueryWeaver' to 'The User is using QueryWeaver' simplifies the user node summary while removing the f-string prefix that wasn't needed.


118-118: LGTM! Good logging improvements.

The replacement of print statements with proper logging calls improves observability and maintainability. The use of structured logging with parameter substitution is appropriate.

Also applies to: 120-120, 155-157, 173-173, 175-175, 181-182


251-274: LGTM! Concurrent execution is a good improvement.

The refactoring to run episode addition and user information updates concurrently using asyncio.gather will improve performance. The structured task organization is clean and maintainable.


586-587: LGTM! Memory context formatting looks good.

The structured formatting of memory context with clear sections for personal preferences, interaction history, and similar queries will provide good context to the LLM.

Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
Comment thread api/memory/graphiti_tool.py
gkorland added a commit that referenced this pull request Feb 22, 2026
…/typescript-5.7.2

Bump typescript from 5.5.4 to 5.7.2
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.

5 participants