Skip to content

Conversation

@menakaj
Copy link

@menakaj menakaj commented Dec 1, 2025

Purpose

Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc.

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email [email protected] to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter �N/A� plus brief explanation of why there�s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type â��Sentâ�� when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to [email protected] and NOT pasted in this PR. If there is no impact on certification exams, type â��N/Aâ�� and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Quick Start guide enabling one-command platform installation.
    • Introduced automated port forwarding for service access.
    • Added customer support agent sample with flight, hotel, car rental, excursion, and policy tools.
  • Documentation

    • Comprehensive installation and configuration guides.
    • Sample agent deployment and usage documentation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

This PR introduces a comprehensive quick-start installation system for deploying an Agent Management Platform with OpenChoreo on Kubernetes/Kind, plus a sample customer-support agent application demonstrating LangGraph-based agent orchestration with multiple tools (flights, hotels, car rentals, excursions, policies) backed by SQLite.

Changes

Cohort / File(s) Summary
Quick Start Documentation & Configuration
README.md, quick-start/README.md, quick-start/QUICK_START.md, quick-start/kind-config.yaml, quick-start/example-values.yaml
Added comprehensive setup guides covering prerequisites, installation workflows, port-forwarding, validation, uninstallation, and troubleshooting; Kind cluster configuration (v1.32.0 with control-plane and workflow-runner nodes); Helm values scaffold for PostgreSQL, Agent Manager, Console, Ingress, and RBAC.
Installation & Bootstrap Scripts
quick-start/bootstrap.sh, quick-start/install.sh, quick-start/install-helpers.sh
Introduced modular Bash installation orchestration: bootstrap.sh provides one-command setup with flag-driven flows (skip-kind, skip-openchoreo, etc.); install.sh handles platform-specific setup; install-helpers.sh exports 30+ functions for idempotent deployment of Prerequisites, Kind clusters, OpenChoreo stack, and Agent Management Platform with readiness checks and CORS patching.
Service Management Scripts
quick-start/port-forward.sh, quick-start/stop-port-forward.sh, quick-start/uninstall.sh
Added port-forwarding automation for Console, Agent Manager, Traces Observer, Data Prepper, and External Gateway; cleanup script to terminate processes and remove PID files; uninstall script with confirmation prompts, namespace deletion options, and helm release cleanup.
Sample Agent Application – Core
samples/customer-support-agent/agent/agent.py, samples/customer-support-agent/agent/state.py, samples/customer-support-agent/agent/utils.py
Introduced LangGraph-based agent implementation with state management (TypedDict with annotated messages), tool orchestration (Assistant class wrapping Runnables), error handling (ToolMessage fallback), and utility functions for tool node creation and event debugging.
Sample Agent Application – Tools
samples/customer-support-agent/tools/car_rentals.py, samples/customer-support-agent/tools/excursions.py, samples/customer-support-agent/tools/flights.py, samples/customer-support-agent/tools/hotels.py, samples/customer-support-agent/tools/policies.py, samples/customer-support-agent/tools/__init__.py
Added six tool modules implementing domain-specific functions (search/book/update/cancel operations) for car rentals, excursions, and hotels; flight ticket management with validation (passenger ownership, booking feasibility); dynamic policy lookup via in-memory vector store with OpenAI embeddings; all backed by SQLite database queries.
Sample Agent Application – API & Setup
samples/customer-support-agent/app.py, samples/customer-support-agent/main.py, samples/customer-support-agent/setup/db.py, samples/customer-support-agent/requirements.txt
Added FastAPI HTTP service exposing /invocations endpoint for agent execution, uvicorn entry point, database setup utility (remote fetch, backup, date shifting), and dependencies (langgraph, langchain, fastapi, uvicorn, etc.).
Sample Project Configuration
samples/customer-support-agent/README.md, samples/customer-support-agent/openapi.yaml, samples/customer-support-agent/LICENSE, samples/customer-support-agent/.gitignore
Added project documentation, OpenAPI spec (localhost /invocation endpoint), Apache 2.0 license, and Python-specific gitignore patterns.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant FastAPI as FastAPI App
    participant Agent as Agent Graph
    participant LLM as LLM Service
    participant Tools as Tool Executor
    participant DB as SQLite DB
    
    User->>FastAPI: POST /invocations<br/>(thread_id, question, passenger_id)
    FastAPI->>Agent: invoke with config<br/>and initial messages
    Agent->>Agent: entry node: pass to Assistant
    Agent->>LLM: invoke with tools binding<br/>(system prompt + tools)
    LLM-->>Agent: response with tool_calls
    Agent->>Agent: check if tool_calls present
    alt Tool calls detected
        Agent->>Tools: execute tool_calls
        Tools->>DB: query/update operations<br/>(flights, hotels, rentals, etc.)
        DB-->>Tools: results
        Tools-->>Agent: ToolMessage results
        Agent->>Agent: append ToolMessages to state
        Agent->>LLM: invoke again with<br/>conversation history
        LLM-->>Agent: refined response<br/>(may have more tool_calls)
    else No tool calls / Stop
        Agent->>Agent: prepare final answer
    end
    Agent-->>FastAPI: final message content
    FastAPI-->>User: JSON response<br/>(results/answer)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • install-helpers.sh: Large helper library with 30+ functions including orchestration for Kind, OpenChoreo components, and platform deployment; requires careful review of idempotent logic, error handling, and readiness checks.
  • bootstrap.sh and install.sh: Multi-step orchestration scripts with conditional flows and environment variable handling; dependency chain between Kind, OpenChoreo, and platform installation.
  • Agent implementation (agent.py, tools/)**: LangGraph graph construction with tool binding, state management, and LLM invocation patterns; multiple database query patterns across tool modules with varying levels of complexity.
  • Tool modules: Six separate tool modules with SQLite queries, parameter validation, and state mutation; require individual verification of query logic and error handling.
  • Cross-component interaction: Installation system wires multiple Helm releases with validation; agent system chains LLM invocations and tool execution; both require understanding of integration points.

Poem

🐰 A bootstrap script, oh what delight,
Kind clusters and OpenChoreo take flight!
With tools bundled tight, the agent learns,
To book flights and hotels—each query now turns
To wisdom! Vector stores and LLM cheer,
The quick-start's complete—let's deploy it right here! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description consists entirely of unfilled template placeholders with no actual content provided by the author. Fill in all required sections (Purpose, Goals, Approach, User stories, Release note) with meaningful content instead of placeholder text.
Docstring Coverage ⚠️ Warning Docstring coverage is 74.58% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding quick start guides and sample code to the platform.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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: 17

♻️ Duplicate comments (1)
quick-start/README.md (1)

197-197: Verify the TROUBLESHOOTING.md file exists.

This documentation references TROUBLESHOOTING.md in multiple locations (lines 104, 197, 276), but the file is not included in this PR. Ensure it exists or will be added.

This is a duplicate concern with QUICK_START.md. The same verification script applies.

🧹 Nitpick comments (33)
samples/customer-support-agent/LICENSE (1)

1-201: Clarify the licensing strategy for sample applications.

The Apache License 2.0 is appropriate and the file is correctly formatted. However, consider whether each sample application should have its own LICENSE file or if there should be guidance on inherited licensing from the repository root.

If you'd like, I can help document a licensing strategy for samples in a README or CONTRIBUTING guide to clarify whether:

  1. Samples inherit the root LICENSE automatically.
  2. Samples can have their own licensing terms (as done here).
  3. A specific convention should be followed across all samples.

Would you like me to suggest a documentation approach for this?

samples/customer-support-agent/tools/__init__.py (1)

1-1: Consider making the database path configurable.

The database path is hardcoded, which reduces flexibility and assumes a specific working directory. For better deployment flexibility, consider using an environment variable with a fallback.

Apply this diff:

+import os
+
-db = "travel2.sqlite"
+db = os.getenv("AGENT_DB_PATH", "travel2.sqlite")
samples/customer-support-agent/main.py (1)

3-5: Document that binding to 0.0.0.0 is for development only.

Binding to all network interfaces (0.0.0.0) can expose the service to external networks, which may be a security risk. While acceptable for a sample application, this should be documented.

Consider adding a comment or making the host configurable:

+import os
 from app import app

 if __name__ == '__main__':
     import uvicorn
-    uvicorn.run(app, host="0.0.0.0", port=8000)
+    # Note: Binding to 0.0.0.0 is for development/demo purposes only
+    # In production, bind to specific interfaces or use a reverse proxy
+    host = os.getenv("AGENT_HOST", "0.0.0.0")
+    port = int(os.getenv("AGENT_PORT", "8000"))
+    uvicorn.run(app, host=host, port=port)
samples/customer-support-agent/README.md (2)

2-2: Consider using a proper heading instead of emphasis.

The "Generated by AI" text uses emphasis instead of a heading, which is flagged by markdown linters.

Apply this diff:

 # Agent Service
-_Generated by AI_
+
+> Generated by AI

24-29: Add language identifier to code fence.

The code block on line 26 is missing a language identifier, which reduces syntax highlighting and accessibility.

Apply this diff:

 Example `.env` file:
 
-```
+```env
 OPENAI_API_KEY=sk-...
 TAVILY_API_KEY=tvly-...

</blockquote></details>
<details>
<summary>quick-start/kind-config.yaml (1)</summary><blockquote>

`15-17`: **Consider potential issues with /tmp/kind-shared mount.**

Mounting `/tmp/kind-shared` may encounter issues:
- `/tmp` directories are commonly cleaned on system reboot
- Permissions may vary across different host systems
- Not all environments allow mounting from `/tmp`



Consider using a more persistent location or documenting the requirements:

```diff
     extraMounts:
-      - hostPath: /tmp/kind-shared
+      # Note: Ensure this directory exists and has appropriate permissions
+      # Consider using a persistent location outside /tmp for production
+      - hostPath: /tmp/kind-shared
         containerPath: /mnt/shared

Alternatively, use a location like /var/lib/kind-shared or document that users should create the directory before running the Quick Start.

quick-start/stop-port-forward.sh (1)

17-24: Use of eval is safe here but could be refactored.

Line 24 uses eval to execute the dynamically constructed lsof command. While the input is controlled (hardcoded ports array), using eval is generally discouraged. Consider refactoring for clarity.

Apply this diff to avoid eval:

-# Build lsof command with all ports
-LSOF_CMD="lsof"
-for port in "${PORTS[@]}"; do
-    LSOF_CMD="$LSOF_CMD -i :$port"
-done
-
-# Get kubectl processes using these ports
-KUBECTL_PIDS=$(eval "$LSOF_CMD" 2>/dev/null | grep kubectl | awk '{print $2}' | sort -u || true)
+# Get kubectl processes using these ports
+# Build lsof arguments array
+LSOF_ARGS=()
+for port in "${PORTS[@]}"; do
+    LSOF_ARGS+=(-i ":$port")
+done
+
+KUBECTL_PIDS=$(lsof "${LSOF_ARGS[@]}" 2>/dev/null | grep kubectl | awk '{print $2}' | sort -u || true)
quick-start/QUICK_START.md (2)

5-5: Fix heading level increment.

Markdown best practices require heading levels to increment by one at a time. The jump from h1 (line 1) to h3 (line 5) should be corrected.

Apply this diff:

-### Prerequisites
+## Prerequisites

59-62: Convert bare URLs to proper markdown links.

The bare URLs should be formatted as proper markdown links for better rendering and accessibility.

Apply this diff:

-- **Console**: http://localhost:3000
-- **API**: http://localhost:8080
-- **Traces Observer**: http://localhost:9098
-- **Data Prepper**: http://localhost:21893
+- **Console**: <http://localhost:3000>
+- **API**: <http://localhost:8080>
+- **Traces Observer**: <http://localhost:9098>
+- **Data Prepper**: <http://localhost:21893>
samples/customer-support-agent/openapi.yaml (1)

1-30: Consider adding security definitions for production use.

The OpenAPI specification lacks security definitions (global security field and operation-level security). While this may be acceptable for a sample/demo application, production APIs should define authentication and authorization schemes.

Consider adding security definitions if this will be used beyond local development:

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []
quick-start/README.md (1)

43-46: Convert bare URLs to proper markdown links.

Similar to QUICK_START.md, these bare URLs should be formatted as proper markdown links for consistency and better rendering.

Apply this diff:

-- Console: http://localhost:3000
-- API: http://localhost:8080
-- Traces Observer: http://localhost:9098
-- Data Prepper: http://localhost:21893
+- Console: <http://localhost:3000>
+- API: <http://localhost:8080>
+- Traces Observer: <http://localhost:9098>
+- Data Prepper: <http://localhost:21893>
quick-start/port-forward.sh (1)

23-71: Consider explicitly using PID variables for clarity.

Shellcheck flags the PID variables as unused, but they are implicitly used by the wait command (line 71) which waits for all background processes. For better clarity and explicit process management, consider passing the PIDs to wait or storing them in an array.

For example:

# Collect PIDs in an array
PIDS=()

kubectl port-forward -n "$AMP_NS" svc/agent-management-platform-console 3000:3000 &
PIDS+=($!)

# ... other port-forwards ...

# Wait for all processes explicitly
for pid in "${PIDS[@]}"; do
    wait "$pid" 2>/dev/null || true
done

Alternatively, the current implementation works correctly, as wait without arguments waits for all background jobs.

quick-start/example-values.yaml (2)

8-8: Strengthen the example password.

While this is an example file, using a weak password like "changeme-secure-password" might be copied as-is. Consider using a stronger example or a placeholder that's obviously invalid.

-    password: "changeme-secure-password"
+    password: "REPLACE_WITH_SECURE_PASSWORD_MIN_16_CHARS"

46-46: CORS wildcard is a security risk.

The CORS configuration allows all origins (*), which is explicitly noted to change for production. However, even in examples, this could be copied without modification.

Consider providing a more specific example:

-    corsAllowedOrigin: "*"  # Change to specific domain in production
+    corsAllowedOrigin: "https://your-domain.com"  # Specify your domain(s)
quick-start/bootstrap.sh (1)

418-441: Consider adding cleanup trap for port-forward process.

The port-forward process is started in the background (line 421) and its PID is saved (line 425), but if the bootstrap script is interrupted or fails after this point, the port-forward process may be left running. Consider adding a trap handler to clean up background processes on script exit.

Add a cleanup function and trap at the beginning of the script:

# After line 23 (after sourcing install-helpers.sh)
cleanup() {
    if [[ -f "${SCRIPT_DIR}/.port-forward.pid" ]]; then
        PORT_FORWARD_PID=$(cat "${SCRIPT_DIR}/.port-forward.pid")
        kill "$PORT_FORWARD_PID" 2>/dev/null || true
        rm -f "${SCRIPT_DIR}/.port-forward.pid"
    fi
}

trap cleanup EXIT INT TERM
samples/customer-support-agent/app.py (2)

45-45: Blocking synchronous call in async endpoint.

run_agent performs synchronous streaming which will block the async event loop. For a sample application this is acceptable, but in production consider running in a thread pool:

import asyncio
result = await asyncio.to_thread(run_agent, payload.thread_id, payload.question, payload.passenger_id)

31-39: Consider using logging instead of print for observability.

The print statement on line 35 should use Python's logging module for better control over log levels and output.

+import logging
+
+logger = logging.getLogger(__name__)
+
     final_answer = None
     for event in events:
         # Each event is a dict representing a streamed update
         if "messages" in event:
-            print(f"Received answer: {event}")
+            logger.debug(f"Received answer: {event}")
             # Keep updating until we get the latest assistant message
             final_answer = event["messages"][-1].content
samples/customer-support-agent/setup/db.py (3)

26-27: Remove unused cursor variable.

The cursor variable is assigned but never used since all queries use pd.read_sql.

     conn = sqlite3.connect(file)
-    cursor = conn.cursor()

33-34: Table name interpolation in SQL query.

While the table names originate from sqlite_master (trusted source), using f-string interpolation for SQL queries is generally discouraged. In this specific case, the risk is low since the input is controlled.

For added safety, you could validate table names against an allowlist:

EXPECTED_TABLES = {"flights", "bookings", "car_rentals", "hotels", "excursions"}
for t in tables:
    if t in EXPECTED_TABLES:
        tdf[t] = pd.read_sql(f"SELECT * from {t}", conn)

58-65: Unnecessary explicit deletion of local variables.

The del df and del tdf statements on lines 60-61 are unnecessary as these variables will be garbage collected when the function returns.

     for table_name, df in tdf.items():
         df.to_sql(table_name, conn, if_exists="replace", index=False)
-    del df
-    del tdf
     conn.commit()
     conn.close()
samples/customer-support-agent/agent/agent.py (1)

82-85: Move imports to top of file.

Imports inside the function body are unconventional and can cause subtle issues with import order. Move these to the top of the file.

+from langgraph.checkpoint.memory import InMemorySaver
+from langgraph.graph import StateGraph, START
+from langgraph.prebuilt import tools_condition
+
 # ... at top of file with other imports

Then remove lines 82-85 from inside create_agent().

samples/customer-support-agent/tools/car_rentals.py (2)

66-77: Use context manager for connection handling to ensure cleanup.

The connection is closed in both branches of the if/else, which is error-prone. Using a context manager or try/finally ensures the connection is always closed.

-    conn = sqlite3.connect(db)
-    cursor = conn.cursor()
-
-    cursor.execute("UPDATE car_rentals SET booked = 1 WHERE id = ?", (rental_id,))
-    conn.commit()
-
-    if cursor.rowcount > 0:
-        conn.close()
-        return f"Car rental {rental_id} successfully booked."
-    else:
-        conn.close()
-        return f"No car rental found with ID {rental_id}."
+    with sqlite3.connect(db) as conn:
+        cursor = conn.cursor()
+        cursor.execute("UPDATE car_rentals SET booked = 1 WHERE id = ?", (rental_id,))
+        conn.commit()
+        if cursor.rowcount > 0:
+            return f"Car rental {rental_id} successfully booked."
+        return f"No car rental found with ID {rental_id}."

This pattern should be applied to all functions in this file.


43-52: Unused function parameters are intentional per comment.

The price_tier, start_date, and end_date parameters are documented but intentionally unused (lines 43-44 explain this is for tutorial simplicity). Consider documenting this in the docstring or removing the parameters if they won't be implemented.

Either update the docstring to note these are placeholders, or remove them to avoid confusion:

     # For our tutorial, we will let you match on any dates and price tier.
     # (since our toy dataset doesn't have much data)
+    # Note: price_tier, start_date, end_date parameters are accepted but not used in filtering
samples/customer-support-agent/tools/excursions.py (2)

26-51: Tighten row mapping and consider small robustness tweaks in search_trip_recommendations.

The logic is sound, but a couple of small improvements would help:

  • Capture column_names before closing the connection and use zip(..., strict=True) to address the Ruff B905 warning and fail fast if column/row lengths ever diverge.
  • Optionally, trim and drop empty keywords to avoid LIKE '%%' clauses when keywords has stray commas.

For example:

-    cursor.execute(query, params)
-    results = cursor.fetchall()
-
-    conn.close()
-
-    return [
-        dict(zip([column[0] for column in cursor.description], row)) for row in results
-    ]
+    cursor.execute(query, params)
+    rows = cursor.fetchall()
+    column_names = [column[0] for column in cursor.description]
+
+    conn.close()
+
+    return [
+        dict(zip(column_names, row, strict=True)) for row in rows
+    ]

And, if you want to harden keyword handling:

-    if keywords:
-        keyword_list = keywords.split(",")
+    if keywords:
+        keyword_list = [kw.strip() for kw in keywords.split(",") if kw.strip()]
+        if not keyword_list:
+            cursor.execute(query, params)
+            ...

55-134: Use context managers or try/finally for SQLite connections in the write helpers.

book_excursion, update_excursion, and cancel_excursion all open a connection and then close it only on the happy/“no row” branches. Any exception between connect and the final conn.close() (e.g., DB locked, schema mismatch) will leak the connection.

To make these helpers more robust and DRY, consider:

  • Wrapping DB access in with sqlite3.connect(db) as conn: and using conn.cursor() inside, or
  • Using try: ... finally: conn.close() around each operation.

For example (pattern applies to all three):

-    conn = sqlite3.connect(db)
-    cursor = conn.cursor()
-    ...
-    conn.commit()
-
-    if cursor.rowcount > 0:
-        conn.close()
-        return ...
-    else:
-        conn.close()
-        return ...
+    conn = sqlite3.connect(db)
+    try:
+        cursor = conn.cursor()
+        ...
+        conn.commit()
+
+        if cursor.rowcount > 0:
+            return ...
+        return ...
+    finally:
+        conn.close()
samples/customer-support-agent/tools/policies.py (2)

12-18: Add a timeout to the requests.get call to avoid hanging at import time.

Because this HTTP fetch runs at module import, a slow or unreachable endpoint will hang the entire process indefinitely.

You can keep behavior the same while bounding the wait:

-response = requests.get(
-    "https://storage.googleapis.com/benchmarks-artifacts/travel-db/swiss_faq.md"
-)
+response = requests.get(
+    "https://storage.googleapis.com/benchmarks-artifacts/travel-db/swiss_faq.md",
+    timeout=10,
+)

48-56: Be aware of heavy side effects at import time (OpenAI embeddings + HTTP).

retriever = VectorStoreRetriever.from_docs(docs, openai.Client()) runs at import, which means:

  • A remote HTTP fetch and
  • A full embeddings call for all docs

happen before any tool is used. This is fine for a small demo, but in larger or test environments it can slow startup or make import brittle.

Consider deferring retriever construction until first lookup_policy call (or behind a small initialization function) if you need more control over startup behavior.

samples/customer-support-agent/tools/hotels.py (1)

10-51: Clarify or implement price_tier/date filters in search_hotels.

The signature and docstring advertise filtering by price_tier, checkin_date, and checkout_date, but the current query only uses location and name. That can confuse tool users and also trips Ruff’s unused‑argument checks.

Options:

  • If this is intentionally simplified for the tutorial, call that out in the docstring and/or prefix the parameters with _ to silence unused‑arg warnings.
  • If you plan to support real filtering, extend the query and parameter list accordingly.

Also, to address the B905 lint and align with other modules, you can tighten the mapping:

-    cursor.execute(query, params)
-    results = cursor.fetchall()
-
-    conn.close()
-
-    return [
-        dict(zip([column[0] for column in cursor.description], row)) for row in results
-    ]
+    cursor.execute(query, params)
+    rows = cursor.fetchall()
+    column_names = [column[0] for column in cursor.description]
+
+    conn.close()
+
+    return [
+        dict(zip(column_names, row, strict=True)) for row in rows
+    ]
samples/customer-support-agent/tools/flights.py (3)

25-53: Row-to-dict mapping in fetch_user_flight_information looks good; minor lint-only tweak available.

The join and mapping logic are correct. To align with Ruff B905 and other modules, you can precompute column names and use zip(..., strict=True):

-    rows = cursor.fetchall()
-    column_names = [column[0] for column in cursor.description]
-    results = [dict(zip(column_names, row)) for row in rows]
+    rows = cursor.fetchall()
+    column_names = [column[0] for column in cursor.description]
+    results = [dict(zip(column_names, row, strict=True)) for row in rows]

65-92: Same optional zip(strict=True) improvement for search_flights.

As above, the query and mapping logic are fine. If you want to satisfy B905 consistently:

-    rows = cursor.fetchall()
-    column_names = [column[0] for column in cursor.description]
-    results = [dict(zip(column_names, row)) for row in rows]
+    rows = cursor.fetchall()
+    column_names = [column[0] for column in cursor.description]
+    results = [dict(zip(column_names, row, strict=True)) for row in rows]

121-123: Optional: apply the same zip(strict=True) pattern when building new_flight_dict.

Not a bug, just consistency and a bit of safety:

-    column_names = [column[0] for column in cursor.description]
-    new_flight_dict = dict(zip(column_names, new_flight))
+    column_names = [column[0] for column in cursor.description]
+    new_flight_dict = dict(zip(column_names, new_flight, strict=True))
quick-start/install-helpers.sh (2)

105-122: Reconsider always returning success from wait_for_deployment.

wait_for_deployment logs a warning when kubectl wait fails, but still returns 0:

    if kubectl wait ...; then
        ...
        return 0
    else
        log_warning "Deployment '$deployment' may still be starting"
        ...
        return 0
    fi

Callers like install_agent_management_platform and install_observability_dataprepper rely on this for readiness gating; masking failures here can let the script continue even when critical deployments never became available.

Two options:

  • Make failures fatal by returning non‑zero in the else branch for critical components, or
  • Add a parameter (e.g., fatal=true|false) so callers can decide per‑deployment whether a miss is fatal or just a warning.

230-249: Ensure the background pod monitor is cleaned up on early failures.

install_agent_management_platform starts a background monitor:

(
  ...
) &
local monitor_pid=$!
...
install_remote_helm_chart ...
...
kill $monitor_pid 2>/dev/null || true

If install_remote_helm_chart (or a later step) fails and exits the script (because of set -e), the kill line is never reached, and the background kubectl get pods loop can keep running in the user’s shell session.

Possible fixes:

  • Track monitor_pid in a global variable and have cleanup() kill it if set, or
  • Use a subshell for the entire install flow so background jobs terminate with it.

For example:

MONITOR_PID=""

install_agent_management_platform() {
  ...
  (
    ...
  ) &
  MONITOR_PID=$!
  ...
}

cleanup() {
  if [[ -n "$MONITOR_PID" ]]; then
    kill "$MONITOR_PID" 2>/dev/null || true
  fi
  log_info "Cleanup complete"
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79f14b2 and 5ccb61f.

📒 Files selected for processing (28)
  • README.md (1 hunks)
  • quick-start/QUICK_START.md (1 hunks)
  • quick-start/README.md (1 hunks)
  • quick-start/bootstrap.sh (1 hunks)
  • quick-start/example-values.yaml (1 hunks)
  • quick-start/install-helpers.sh (1 hunks)
  • quick-start/install.sh (1 hunks)
  • quick-start/kind-config.yaml (1 hunks)
  • quick-start/port-forward.sh (1 hunks)
  • quick-start/stop-port-forward.sh (1 hunks)
  • quick-start/uninstall.sh (1 hunks)
  • samples/customer-support-agent/.gitignore (1 hunks)
  • samples/customer-support-agent/LICENSE (1 hunks)
  • samples/customer-support-agent/README.md (1 hunks)
  • samples/customer-support-agent/agent/agent.py (1 hunks)
  • samples/customer-support-agent/agent/state.py (1 hunks)
  • samples/customer-support-agent/agent/utils.py (1 hunks)
  • samples/customer-support-agent/app.py (1 hunks)
  • samples/customer-support-agent/main.py (1 hunks)
  • samples/customer-support-agent/openapi.yaml (1 hunks)
  • samples/customer-support-agent/requirements.txt (1 hunks)
  • samples/customer-support-agent/setup/db.py (1 hunks)
  • samples/customer-support-agent/tools/__init__.py (1 hunks)
  • samples/customer-support-agent/tools/car_rentals.py (1 hunks)
  • samples/customer-support-agent/tools/excursions.py (1 hunks)
  • samples/customer-support-agent/tools/flights.py (1 hunks)
  • samples/customer-support-agent/tools/hotels.py (1 hunks)
  • samples/customer-support-agent/tools/policies.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
samples/customer-support-agent/app.py (1)
samples/customer-support-agent/agent/agent.py (1)
  • create_agent (41-104)
quick-start/uninstall.sh (1)
quick-start/install-helpers.sh (6)
  • log_error (45-47)
  • log_info (33-35)
  • log_success (37-39)
  • helm_release_exists (61-65)
  • namespace_exists (55-58)
  • log_warning (41-43)
quick-start/bootstrap.sh (1)
quick-start/install-helpers.sh (9)
  • log_error (45-47)
  • log_info (33-35)
  • verify_openchoreo_prerequisites (742-779)
  • log_success (37-39)
  • setup_kind_cluster (487-547)
  • install_openchoreo_core (683-739)
  • install_agent_management_platform_silent (332-351)
  • install_observability_dataprepper_silent (314-329)
  • log_warning (41-43)
🪛 Checkov (3.2.334)
samples/customer-support-agent/openapi.yaml

[high] 1-32: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)


[high] 1-32: Ensure that security operations is not empty.

(CKV_OPENAPI_5)

🪛 LanguageTool
quick-start/QUICK_START.md

[style] ~98-~98: Consider using “inaccessible” to avoid wordiness.
Context: ... ./install.sh --verbose **Services not accessible?** Check port forwarding:bash kubec...

(NOT_ABLE_PREMIUM)

samples/customer-support-agent/README.md

[grammar] ~22-~22: Ensure spelling is correct
Context: ...penAI API key - TRIVIY_API_KEY - your Triviy API key Example .env file: ``` OPEN...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

quick-start/README.md

[style] ~22-~22: Consider replacing this word to strengthen your wording.
Context: ...te**: Observability is a core component and is always installed, not optional. ---...

(AND_THAT)

🪛 markdownlint-cli2 (0.18.1)
quick-start/QUICK_START.md

5-5: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)


59-59: Bare URL used

(MD034, no-bare-urls)


60-60: Bare URL used

(MD034, no-bare-urls)


61-61: Bare URL used

(MD034, no-bare-urls)


62-62: Bare URL used

(MD034, no-bare-urls)

samples/customer-support-agent/README.md

2-2: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


6-6: Bare URL used

(MD034, no-bare-urls)


26-26: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

quick-start/README.md

43-43: Bare URL used

(MD034, no-bare-urls)


44-44: Bare URL used

(MD034, no-bare-urls)


45-45: Bare URL used

(MD034, no-bare-urls)


46-46: Bare URL used

(MD034, no-bare-urls)

🪛 Ruff (0.14.6)
samples/customer-support-agent/tools/excursions.py

50-50: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

samples/customer-support-agent/tools/policies.py

12-12: Probable use of requests call without timeout

(S113)

samples/customer-support-agent/agent/utils.py

14-14: Use explicit conversion flag

Replace with conversion flag

(RUF010)

samples/customer-support-agent/tools/hotels.py

14-14: Unused function argument: price_tier

(ARG001)


15-15: Unused function argument: checkin_date

(ARG001)


16-16: Unused function argument: checkout_date

(ARG001)


50-50: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

samples/customer-support-agent/setup/db.py

15-15: Probable use of requests call without timeout

(S113)


27-27: Local variable cursor is assigned to but never used

Remove assignment to unused variable cursor

(F841)


34-34: Possible SQL injection vector through string-based query construction

(S608)

samples/customer-support-agent/main.py

5-5: Possible binding to all interfaces

(S104)

samples/customer-support-agent/agent/agent.py

9-9: from tools.car_rentals import * used; unable to detect undefined names

(F403)


10-10: from tools.excursions import * used; unable to detect undefined names

(F403)


11-11: from tools.flights import * used; unable to detect undefined names

(F403)


12-12: from tools.hotels import * used; unable to detect undefined names

(F403)


13-13: from tools.policies import * used; unable to detect undefined names

(F403)


15-15: load_dotenv may be undefined, or defined from star imports

(F405)


21-21: RunnableConfig may be undefined, or defined from star imports

(F405)


31-32: Parenthesize a and b expressions when chaining and and or together, to make the precedence clear

Parenthesize the and subexpression

(RUF021)


57-57: datetime may be undefined, or defined from star imports

(F405)


61-61: fetch_user_flight_information may be undefined, or defined from star imports

(F405)


62-62: search_flights may be undefined, or defined from star imports

(F405)


63-63: lookup_policy may be undefined, or defined from star imports

(F405)


64-64: update_ticket_to_new_flight may be undefined, or defined from star imports

(F405)


65-65: cancel_ticket may be undefined, or defined from star imports

(F405)


66-66: search_car_rentals may be undefined, or defined from star imports

(F405)


67-67: book_car_rental may be undefined, or defined from star imports

(F405)


68-68: update_car_rental may be undefined, or defined from star imports

(F405)


69-69: cancel_car_rental may be undefined, or defined from star imports

(F405)


70-70: search_hotels may be undefined, or defined from star imports

(F405)


71-71: book_hotel may be undefined, or defined from star imports

(F405)


72-72: update_hotel may be undefined, or defined from star imports

(F405)


73-73: cancel_hotel may be undefined, or defined from star imports

(F405)


74-74: search_trip_recommendations may be undefined, or defined from star imports

(F405)


75-75: book_excursion may be undefined, or defined from star imports

(F405)


76-76: update_excursion may be undefined, or defined from star imports

(F405)


77-77: cancel_excursion may be undefined, or defined from star imports

(F405)

samples/customer-support-agent/tools/flights.py

23-23: Avoid specifying long messages outside the exception class

(TRY003)


48-48: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


91-91: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


107-107: Avoid specifying long messages outside the exception class

(TRY003)


122-122: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)


174-174: Avoid specifying long messages outside the exception class

(TRY003)

samples/customer-support-agent/tools/car_rentals.py

14-14: Unused function argument: price_tier

(ARG001)


15-15: Unused function argument: start_date

(ARG001)


16-16: Unused function argument: end_date

(ARG001)


51-51: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

🪛 Shellcheck (0.11.0)
quick-start/port-forward.sh

[warning] 23-23: CONSOLE_PID appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 28-28: AGENT_MGR_PID appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 34-34: TRACES_PID appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 43-43: DATAPREPPER_PID appears unused. Verify use (or export if used externally).

(SC2034)


[warning] 52-52: EXTERNAL_GATEWAY_PID appears unused. Verify use (or export if used externally).

(SC2034)

🔇 Additional comments (15)
samples/customer-support-agent/.gitignore (1)

1-206: Comprehensive and well-organized Python .gitignore file.

The file follows GitHub's standard Python .gitignore template with helpful comments and well-organized sections covering Python artifacts, distributions, test outputs, virtual environments, and tool-specific patterns (Abstra, VS Code, Ruff, Cursor, Marimo). The structure is excellent for a Python sample project.

samples/customer-support-agent/agent/utils.py (2)

22-25: LGTM!

The fallback pattern correctly wraps the ToolNode with error handling via RunnableLambda.


28-41: LGTM!

The debug utility correctly tracks printed message IDs to avoid duplication and truncates long representations for readability.

README.md (1)

4-6: LGTM!

Clear and concise Quick Start section that guides users to the appropriate documentation.

quick-start/stop-port-forward.sh (1)

1-71: LGTM!

The script has robust error handling, provides clear user feedback, and uses multiple cleanup methods to ensure port-forward processes are terminated. The three-method approach (lsof, PID file, pgrep/pkill) ensures thorough cleanup.

quick-start/kind-config.yaml (1)

8-8: File does not exist in repository.

The file quick-start/kind-config.yaml is not present in this repository. The review comment cannot be applied as there is no file to review at the specified location.

Likely an incorrect or invalid review comment.

samples/customer-support-agent/agent/state.py (1)

1-9: LGTM!

The State TypedDict is correctly defined following LangGraph conventions. The messages field with add_messages annotation is the standard pattern for managing conversation state.

quick-start/uninstall.sh (1)

1-132: LGTM with one concern addressed above.

The uninstall script is well-structured with proper confirmation prompts, safety guards, and clear user feedback. The only issue is the overly broad pkill command flagged above.

quick-start/QUICK_START.md (1)

104-104: The file being reviewed does not exist in the repository.

The review references quick-start/QUICK_START.md and TROUBLESHOOTING.md, but neither file exists in this repository. A search of all tracked and untracked files confirms these files are not present. The documentation and directory structure referenced in this review comment do not match the actual repository content.

Likely an incorrect or invalid review comment.

quick-start/bootstrap.sh (1)

220-220: Cannot verify path reference—files do not exist in repository.

The review comment references quick-start/bootstrap.sh at line 220, but this file and the related deployments/ directory do not exist in the current repository state. Manual verification is required when the code is available in the actual PR or branch context.

quick-start/install.sh (3)

1-3: Good use of strict mode.

Using set -eo pipefail ensures the script fails fast on errors and catches failures in pipelines. This is a best practice for installation scripts.


67-69: The referenced files do not exist in the repository.

The review comment references quick-start/install.sh (lines 67-69) and quick-start/install-helpers.sh, but neither file exists in the repository. The codebase currently contains only configuration files (.gitignore, LICENSE, README.md, etc.). This review comment cannot be addressed as written.

Likely an incorrect or invalid review comment.


124-132: File and code referenced in this comment do not exist in the repository.

The quick-start/install.sh file, the port-forwarding code at lines 124-132, and the stop-port-forward.sh script do not exist in this repository. This repository currently contains only template and configuration files with no shell scripts or quick-start directories.

Likely an incorrect or invalid review comment.

samples/customer-support-agent/tools/flights.py (1)

109-131: The review comment references a file that does not exist in this repository.

The file samples/customer-support-agent/tools/flights.py is not present in the repository at any branch. The review comment cannot be applied to this codebase.

Verify that the review is intended for this repository and branch. If this review was meant for a different repository or pull request, please provide the correct target location.

Likely an incorrect or invalid review comment.

samples/customer-support-agent/tools/policies.py (1)

21-45: The review comment references samples/customer-support-agent/tools/policies.py, which does not exist in this repository. The file and VectorStoreRetriever class cannot be located. This comment cannot be applied to the current codebase.

Likely an incorrect or invalid review comment.

Comment on lines +102 to +108
rbac:
create: true
# Full permissions for agent management operations
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Overly permissive RBAC rules pose a security risk.

The RBAC configuration grants full permissions across all API groups, resources, and verbs (apiGroups: ["*"], resources: ["*"], verbs: ["*"]). This is a cluster-admin level of access and violates the principle of least privilege. Even in example files, this can be copied by users into production environments.

Apply this diff to provide a more appropriate example scoped to agent management:

 # RBAC Configuration
 rbac:
   create: true
-  # Full permissions for agent management operations
+  # Scoped permissions for agent management operations
   rules:
-    - apiGroups: ["*"]
-      resources: ["*"]
-      verbs: ["*"]
+    - apiGroups: [""]
+      resources: ["pods", "services", "configmaps", "secrets"]
+      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
+    - apiGroups: ["apps"]
+      resources: ["deployments", "statefulsets"]
+      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
+    - apiGroups: ["batch"]
+      resources: ["jobs", "cronjobs"]
+      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +68 to +103
wait_for_pods() {
local namespace="$1"
local timeout="${2:-300}" # 5 minutes default
local label_selector="${3:-}"

log_info "Waiting for pods in namespace '$namespace' to be ready..."

local selector_flag=""
if [[ -n "$label_selector" ]]; then
selector_flag="-l $label_selector"
fi

if ! timeout "$timeout" bash -c "
while true; do
pods=\$(kubectl get pods -n '$namespace' $selector_flag --no-headers 2>/dev/null || true)
if [[ -z \"\$pods\" ]]; then
echo 'No pods found yet, waiting...'
sleep 5
continue
fi
if echo \"\$pods\" | grep -v 'Running\|Completed' | grep -q .; then
echo 'Waiting for pods to be ready...'
sleep 5
else
echo 'All pods are ready!'
break
fi
done
"; then
log_error "Timeout waiting for pods in namespace '$namespace'"
kubectl get pods -n "$namespace" $selector_flag 2>/dev/null || true
return 1
fi

log_success "All pods in namespace '$namespace' are ready"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a prereq check (or fallback) for timeout in wait_for_pods.

wait_for_pods assumes a GNU timeout binary is available:

if ! timeout "$timeout" bash -c "..."

On macOS (without coreutils) or other environments without timeout, this will cause an immediate failure despite verify_prerequisites passing.

Consider either:

  • Adding timeout to the prerequisites check and documenting it, or
  • Falling back to a plain loop when timeout is not present, e.g.:
if ! command_exists timeout; then
  # simple, unbounded wait loop as a fallback
  while true; do
    ...
  done
else
  if ! timeout "$timeout" bash -c '...'; then
    ...
  fi
fi
🤖 Prompt for AI Agents
In quick-start/install-helpers.sh around lines 68 to 103, the wait_for_pods
function assumes the GNU timeout binary exists which breaks on macOS/other
systems; add a prerequisite check for timeout at script startup (or include it
in verify_prerequisites) and modify wait_for_pods to fallback when timeout is
missing: detect with command -v timeout (or helper command_exists) and if absent
run the existing pod-wait loop without timeout or implement a simple manual
timeout using SECONDS to track elapsed time and exit with failure after the
configured timeout; ensure logs and exit codes remain unchanged when the
fallback path is used.

Comment on lines +25 to +32
--config)
if [[ -f "$2" ]]; then
AMP_HELM_ARGS+=("-f" "$2")
else
log_error "Config file not found: $2"
exit 1
fi
shift 2
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Array AMP_HELM_ARGS used before initialization.

The AMP_HELM_ARGS array is appended to on line 27 but never initialized. This could cause issues depending on bash version and settings.

Initialize the array early in the script:

 # Configuration
 VERBOSE="${VERBOSE:-false}"
 AUTO_PORT_FORWARD="${AUTO_PORT_FORWARD:-true}"
+AMP_HELM_ARGS=()

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In quick-start/install.sh around lines 25 to 32, AMP_HELM_ARGS is appended to on
line 27 but never initialized; initialize AMP_HELM_ARGS as an empty array
(AMP_HELM_ARGS=()) near the top of the script before any argument parsing so
subsequent AMP_HELM_ARGS+=() operations are safe across bash versions and
settings.

fi

# Kill all kubectl port-forward processes
pkill -f "kubectl port-forward" 2>/dev/null || true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Narrow the pkill scope to avoid terminating unrelated processes.

The command pkill -f "kubectl port-forward" will terminate ALL kubectl port-forward processes on the system, including those unrelated to this installation. This could disrupt other development workflows.

Consider limiting the cleanup to only the port-forwards managed by this script, or at least warn the user:

-# Kill all kubectl port-forward processes
-pkill -f "kubectl port-forward" 2>/dev/null || true
-log_success "All port forwarding processes stopped"
+# Kill port-forwards related to this installation
+log_warning "Stopping port-forward processes for agent-management-platform..."
+pkill -f "kubectl port-forward.*agent-management-platform" 2>/dev/null || true
+pkill -f "kubectl port-forward.*openchoreo-observability-plane" 2>/dev/null || true
+pkill -f "kubectl port-forward.*openchoreo-data-plane" 2>/dev/null || true
+log_success "Port forwarding processes stopped"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In quick-start/uninstall.sh around line 82, the pkill invocation is too broad
and will kill all kubectl port-forward processes system-wide; change it to only
target port-forwards started by this script by storing and later killing their
PIDs (save each kubectl port-forward PID when launched to a temp file and
read/kill those PIDs here), or at minimum narrow the match to a unique
identifier used by this script (e.g., include a specific port, namespace, label,
or unique command fragment) and warn the user before killing; implement one of
these approaches and ensure the cleanup handles missing PIDs gracefully.

*.sage.py

# Environments
../.env
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reconsider the relative path pattern for .env.

The pattern ../.env on Line 138 uses a relative path traversal to target the parent directory. However, .gitignore patterns are relative to the directory containing the .gitignore file, so this pattern would only ignore samples/.env, which may not be the intended behavior. If the goal is to ignore .env files at the project root, this pattern won't work as expected because .gitignore does not allow patterns to traverse above its containing directory through ...

Consider removing the ../ prefix and using only .env if the intent is to ignore .env files in this sample directory.

Apply this diff to fix the pattern:

-../.env
+.env
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
../.env
.env
🤖 Prompt for AI Agents
In samples/customer-support-agent/.gitignore around line 138, the pattern
"../.env" incorrectly attempts to traverse to the parent directory (gitignore
patterns are relative to the file's directory); replace that entry with ".env"
(or remove the "../" prefix) so the sample's .env is ignored as intended, and
commit the updated .gitignore.

Comment on lines +19 to +29
The service requires two API keys (set these as environment variables or in a `.env` file in the project root):

- `OPENAI_API_KEY` - your OpenAI API key
- `TRIVIY_API_KEY` - your Triviy API key

Example `.env` file:

```
OPENAI_API_KEY=sk-...
TRIVIY_API_KEY=triviy-...
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix typo in API key name.

The API key is misspelled as "TRIVIY_API_KEY" when it should be "TAVILY_API_KEY" (based on the tavily-python dependency). This will cause runtime failures when users follow the documentation.

Apply this diff:

 - `OPENAI_API_KEY` - your OpenAI API key
-- `TRIVIY_API_KEY` - your Triviy API key
+- `TAVILY_API_KEY` - your Tavily API key
 
 Example `.env` file:
 

OPENAI_API_KEY=sk-...
-TRIVIY_API_KEY=triviy-...
+TAVILY_API_KEY=tvly-...

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The service requires two API keys (set these as environment variables or in a `.env` file in the project root):
- `OPENAI_API_KEY` - your OpenAI API key
- `TRIVIY_API_KEY` - your Triviy API key
Example `.env` file:
```
OPENAI_API_KEY=sk-...
TRIVIY_API_KEY=triviy-...
```
The service requires two API keys (set these as environment variables or in a `.env` file in the project root):
- `OPENAI_API_KEY` - your OpenAI API key
- `TAVILY_API_KEY` - your Tavily API key
Example `.env` file:
🧰 Tools
🪛 LanguageTool

[grammar] ~22-~22: Ensure spelling is correct
Context: ...penAI API key - TRIVIY_API_KEY - your Triviy API key Example .env file: ``` OPEN...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)

26-26: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In samples/customer-support-agent/README.md around lines 19 to 29, the README
contains a typo in the environment variable name and example value — it uses
TRIVIY_API_KEY/triviy-... but the project depends on tavily-python so the
correct env var is TAVILY_API_KEY and example value prefix should be tvly-....
Update the README and example .env to replace TRIVIY_API_KEY with TAVILY_API_KEY
and change the sample key value to tvly-... so users set the correct variable
name and avoid runtime failures.

Comment on lines +1 to +8
langgraph
langchain-community
langchain_openai
tavily-python
pandas
openai
fastapi
uvicorn
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest versions of dependencies on PyPI
for pkg in langgraph langchain-community langchain_openai tavily-python pandas openai fastapi uvicorn; do
  echo "=== $pkg ==="
  curl -s "https://pypi.org/pypi/$pkg/json" | jq -r '.info.version'
done

Repository: wso2/ai-agent-management-platform

Length of output: 283


Pin dependency versions for reproducibility and security.

All dependencies are unpinned, which can lead to version drift, broken builds, and potential security vulnerabilities. This affects reproducibility and supply chain security.

Apply this diff to pin versions to current stable releases:

-langgraph
-langchain-community
-langchain_openai
-tavily-python
-pandas
-openai
-fastapi
-uvicorn
+langgraph==1.0.4
+langchain-community==0.4.1
+langchain_openai==1.1.0
+tavily-python==0.7.13
+pandas==2.3.3
+openai==2.8.1
+fastapi==0.123.0
+uvicorn[standard]==0.38.0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
langgraph
langchain-community
langchain_openai
tavily-python
pandas
openai
fastapi
uvicorn
langgraph==1.0.4
langchain-community==0.4.1
langchain_openai==1.1.0
tavily-python==0.7.13
pandas==2.3.3
openai==2.8.1
fastapi==0.123.0
uvicorn[standard]==0.38.0
🤖 Prompt for AI Agents
In samples/customer-support-agent/requirements.txt lines 1–8 the dependencies
are unpinned; replace each entry with an exact, tested version pin
(package==MAJOR.MINOR.PATCH) for reproducibility and security—pin langgraph,
langchain-community, langchain_openai, tavily-python, pandas, openai, fastapi,
and uvicorn to their current stable releases, optionally add integrity hashes or
maintain a separate constraints/lock file, then run the test matrix (and
pip-compile or pip freeze) to verify compatibility and commit the updated
requirements.txt.

Comment on lines +14 to +20
if overwrite or not os.path.exists(local_file):
response = requests.get(db_url)
response.raise_for_status() # Ensure the request was successful
with open(local_file, "wb") as f:
f.write(response.content)
# Backup - we will use this to "reset" our DB in each section
shutil.copy(local_file, backup_file)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add timeout to HTTP request to prevent indefinite hangs.

The requests.get call lacks a timeout, which could cause the script to hang indefinitely if the server is unresponsive.

-    response = requests.get(db_url)
+    response = requests.get(db_url, timeout=30)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if overwrite or not os.path.exists(local_file):
response = requests.get(db_url)
response.raise_for_status() # Ensure the request was successful
with open(local_file, "wb") as f:
f.write(response.content)
# Backup - we will use this to "reset" our DB in each section
shutil.copy(local_file, backup_file)
if overwrite or not os.path.exists(local_file):
response = requests.get(db_url, timeout=30)
response.raise_for_status() # Ensure the request was successful
with open(local_file, "wb") as f:
f.write(response.content)
# Backup - we will use this to "reset" our DB in each section
shutil.copy(local_file, backup_file)
🧰 Tools
🪛 Ruff (0.14.6)

15-15: Probable use of requests call without timeout

(S113)

🤖 Prompt for AI Agents
In samples/customer-support-agent/setup/db.py around lines 14 to 20, the
requests.get call has no timeout which can cause the script to hang; update the
call to include a sensible timeout (e.g., requests.get(db_url, timeout=10)) and
handle potential timeout/network exceptions by catching
requests.exceptions.Timeout and requests.exceptions.RequestException to log or
raise a clear error so the script fails fast instead of hanging.

Comment on lines +100 to +117
if start_date:
cursor.execute(
"UPDATE car_rentals SET start_date = ? WHERE id = ?",
(start_date, rental_id),
)
if end_date:
cursor.execute(
"UPDATE car_rentals SET end_date = ? WHERE id = ?", (end_date, rental_id)
)

conn.commit()

if cursor.rowcount > 0:
conn.close()
return f"Car rental {rental_id} successfully updated."
else:
conn.close()
return f"No car rental found with ID {rental_id}."
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

rowcount check may give incorrect result when both dates are updated.

When both start_date and end_date are provided, cursor.rowcount only reflects the second UPDATE statement. If the first update succeeds but the second finds no rows (shouldn't happen for same ID, but still), the message could be misleading.

Consider tracking success from either update:

+    rows_updated = 0
     if start_date:
         cursor.execute(
             "UPDATE car_rentals SET start_date = ? WHERE id = ?",
             (start_date, rental_id),
         )
+        rows_updated += cursor.rowcount
     if end_date:
         cursor.execute(
             "UPDATE car_rentals SET end_date = ? WHERE id = ?", (end_date, rental_id)
         )
+        rows_updated += cursor.rowcount
 
     conn.commit()
 
-    if cursor.rowcount > 0:
+    if rows_updated > 0:
         conn.close()
         return f"Car rental {rental_id} successfully updated."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if start_date:
cursor.execute(
"UPDATE car_rentals SET start_date = ? WHERE id = ?",
(start_date, rental_id),
)
if end_date:
cursor.execute(
"UPDATE car_rentals SET end_date = ? WHERE id = ?", (end_date, rental_id)
)
conn.commit()
if cursor.rowcount > 0:
conn.close()
return f"Car rental {rental_id} successfully updated."
else:
conn.close()
return f"No car rental found with ID {rental_id}."
rows_updated = 0
if start_date:
cursor.execute(
"UPDATE car_rentals SET start_date = ? WHERE id = ?",
(start_date, rental_id),
)
rows_updated += cursor.rowcount
if end_date:
cursor.execute(
"UPDATE car_rentals SET end_date = ? WHERE id = ?", (end_date, rental_id)
)
rows_updated += cursor.rowcount
conn.commit()
if rows_updated > 0:
conn.close()
return f"Car rental {rental_id} successfully updated."
else:
conn.close()
return f"No car rental found with ID {rental_id}."
🤖 Prompt for AI Agents
In samples/customer-support-agent/tools/car_rentals.py around lines 100 to 117,
the logic uses cursor.rowcount after two separate UPDATEs which only reflects
the last statement; to fix this, track whether any update affected rows by
initializing a variable (e.g., updated = False) before the conditionals, after
each cursor.execute check if cursor.rowcount > 0 and set updated = True, commit
and close the connection, then use the updated flag to determine the success
message (return success if updated is True, otherwise return not found).

Comment on lines +79 to +117
@tool
def update_hotel(
hotel_id: int,
checkin_date: Optional[Union[datetime, date]] = None,
checkout_date: Optional[Union[datetime, date]] = None,
) -> str:
"""
Update a hotel's check-in and check-out dates by its ID.

Args:
hotel_id (int): The ID of the hotel to update.
checkin_date (Optional[Union[datetime, date]]): The new check-in date of the hotel. Defaults to None.
checkout_date (Optional[Union[datetime, date]]): The new check-out date of the hotel. Defaults to None.

Returns:
str: A message indicating whether the hotel was successfully updated or not.
"""
conn = sqlite3.connect(db)
cursor = conn.cursor()

if checkin_date:
cursor.execute(
"UPDATE hotels SET checkin_date = ? WHERE id = ?", (checkin_date, hotel_id)
)
if checkout_date:
cursor.execute(
"UPDATE hotels SET checkout_date = ? WHERE id = ?",
(checkout_date, hotel_id),
)

conn.commit()

if cursor.rowcount > 0:
conn.close()
return f"Hotel {hotel_id} successfully updated."
else:
conn.close()
return f"No hotel found with ID {hotel_id}."

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle “no fields to update” and track affected rows correctly in update_hotel.

Two edge cases here can produce misleading results:

  1. If both checkin_date and checkout_date are None, no UPDATE runs, but the function still returns “No hotel found with ID …”.
  2. cursor.rowcount only reflects the last execute call; if you ever extend this to multiple updates, you’ll want to aggregate.

You can make behavior explicit and slightly more accurate:

-    conn = sqlite3.connect(db)
-    cursor = conn.cursor()
-
-    if checkin_date:
-        cursor.execute(
-            "UPDATE hotels SET checkin_date = ? WHERE id = ?", (checkin_date, hotel_id)
-        )
-    if checkout_date:
-        cursor.execute(
-            "UPDATE hotels SET checkout_date = ? WHERE id = ?",
-            (checkout_date, hotel_id),
-        )
-
-    conn.commit()
-
-    if cursor.rowcount > 0:
-        conn.close()
-        return f"Hotel {hotel_id} successfully updated."
-    else:
-        conn.close()
-        return f"No hotel found with ID {hotel_id}."
+    if checkin_date is None and checkout_date is None:
+        return "No fields provided to update."
+
+    conn = sqlite3.connect(db)
+    try:
+        cursor = conn.cursor()
+        rows_affected = 0
+
+        if checkin_date is not None:
+            cursor.execute(
+                "UPDATE hotels SET checkin_date = ? WHERE id = ?",
+                (checkin_date, hotel_id),
+            )
+            rows_affected += cursor.rowcount
+
+        if checkout_date is not None:
+            cursor.execute(
+                "UPDATE hotels SET checkout_date = ? WHERE id = ?",
+                (checkout_date, hotel_id),
+            )
+            rows_affected += cursor.rowcount
+
+        conn.commit()
+
+        if rows_affected > 0:
+            return f"Hotel {hotel_id} successfully updated."
+        return f"No hotel found with ID {hotel_id}."
+    finally:
+        conn.close()
🤖 Prompt for AI Agents
In samples/customer-support-agent/tools/hotels.py around lines 79 to 117, the
update_hotel function can return misleading results when no fields are provided
and rowcount only reflects the last execute; fix by first checking if neither
checkin_date nor checkout_date is provided and return a clear message like "No
fields to update for hotel {hotel_id}." before opening a DB transaction, and
track affected rows across updates by accumulating a local counter (e.g.,
total_updated += cursor.rowcount) after each execute; finally commit/close and
use the accumulated total to decide whether to report success or "No hotel found
with ID ...".

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.

1 participant