Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ __pycache__/

# Private keys / Secrets
deploy_key

# Scratch files
agent/scratch/*
fix_agent_langfuse.py
fix_langfuse.py

# Playwright
playwright-report/
test-results/
junit.xml
Binary file added agent/.coverage
Binary file not shown.
2 changes: 1 addition & 1 deletion agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 \
# Add virtualenv bin to PATH to run uvicorn
ENV PATH="/app/agent/.venv/bin:$PATH"

CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
Comment on lines 23 to +26

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Drop root before the image starts.

The final container still runs uvicorn as root. Any compromise in agent.main gets full container privileges unnecessarily. Create a dedicated runtime user and switch to it before CMD.

Suggested change
 # Add virtualenv bin to PATH to run uvicorn
 ENV PATH="/app/agent/.venv/bin:$PATH"
+
+RUN addgroup --system app && adduser --system --ingroup app app && \
+    chown -R app:app /app
+USER app
 
 CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
📝 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
# Add virtualenv bin to PATH to run uvicorn
ENV PATH="/app/agent/.venv/bin:$PATH"
CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
# Add virtualenv bin to PATH to run uvicorn
ENV PATH="/app/agent/.venv/bin:$PATH"
RUN addgroup --system app && adduser --system --ingroup app app && \
chown -R app:app /app
USER app
CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]
🧰 Tools
🪛 Checkov (3.3.1)

[low] 1-26: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-26: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/Dockerfile` around lines 23 - 26, The container currently starts
`uvicorn` as root, so update the `Dockerfile` to create a dedicated non-root
runtime user and switch to that user before the `CMD` runs. Make sure the app
files and virtualenv referenced by the existing `PATH`/`uvicorn` setup remain
accessible to that user, and keep the final startup command unchanged aside from
the user context.

Source: Linters/SAST tools

22 changes: 22 additions & 0 deletions agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies = [
"langchain-openai",
"greenlet>=3.5.1",
"mcp>=1.12.4",
"networkx>=3.3",
]

[tool.uv.sources]
Expand All @@ -31,4 +32,25 @@ build-backend = "uv_build"
[dependency-groups]
dev = [
"pytest>=9.0.3",
"pytest-asyncio>=1.4.0",
"pytest-cov>=7.1.0",
"pytest-mock>=3.15.1",
"ruff>=0.3.0",
]

[tool.ruff]
target-version = "py312"

[tool.ruff.lint]
select = ["TID"]

[tool.ruff.lint.flake8-tidy-imports.banned-api]
"langchain_openai.ChatOpenAI" = { msg = "Use get_llm from agent.llm instead of instantiating ChatOpenAI directly." }
"esca_sdk.EscaClient" = { msg = "Use get_esca_client from agent.utils.esca instead of instantiating EscaClient directly." }

[tool.ruff.lint.per-file-ignores]
"src/agent/llm.py" = ["TID251"]
"src/agent/utils/esca.py" = ["TID251"]

[tool.pytest.ini_options]
testpaths = ["tests"]
46 changes: 44 additions & 2 deletions agent/src/agent/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing import Literal

# Reload trigger comment (timeout added)


class AgentSettings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
Expand All @@ -14,19 +18,57 @@ class AgentSettings(BaseSettings):
EMBEDDER_KEY: str = ""
HYBRID_SEARCH_MAX_TABLES: int = 10
MAX_PROFILES_TO_FETCH: int = 3
PROFILE_FETCH_CONCURRENCY: int = Field(default=4, gt=0)
REDIS_URL: str = "redis://localhost:6379"

LANGFUSE_SECRET_KEY: str = Field(min_length=1)
LANGFUSE_PUBLIC_KEY: str = Field(min_length=1)
LANGFUSE_BASE_URL: str = Field(min_length=1)


# ── Jeen Integration ──────────────────────────────────────────────────────
JEEN_LLM_CORE_URL: str = "" # If empty, agent gracefully skips fetching
JEEN_API_KEY: str = "" # If empty, agent gracefully skips fetching
SKILLS_HOT_RELOAD: bool = False # If true, bypass Redis cache for skills

# ── G4: Feature Flags & Execution Modes ──────────────────────────────────
BACKEND_URL: str = "" # Studio backend URL for flag reads (e.g. http://backend:8000)
# If empty, FlagBridge falls back to env-var defaults


# Langfuse prompt names
LANGFUSE_PROMPT_EXTRACTOR: str = "text2sql/extractor"
LANGFUSE_PROMPT_SCHEMA_EXPLORER: str = "text2sql/schema_explorer"
LANGFUSE_PROMPT_QUERY_BUILDER: str = "text2sql/query_builder"
LANGFUSE_PROMPT_REFINER: str = "text2sql/refiner"
LANGFUSE_PROMPT_FINALIZER_SUMMARY: str = "text2sql/finalizer_summary"
LANGFUSE_PROMPT_FINALIZER_SQL_EXPLANATION: str = "text2sql/finalizer_sql_explanation"
LANGFUSE_PROMPT_FINALIZER_SQL_EXPLANATION: str = (
"text2sql/finalizer_sql_explanation"
)
LANGFUSE_PROMPT_REJECTION_ROUTER: str = "text2sql/rejection_router"

# ── G2-01: Table Scoping ──────────────────────────────────────────────────
TABLE_SCOPING_MODE: Literal["strict", "hybrid"] = "hybrid"

# ── G2-03: Advanced Schema Explorer phases ────────────────────────────────
ENABLE_SEMANTIC_TYPING: bool = True # single batched LLM call — adds id/timestamp/category labels
ENABLE_JOIN_GRAPH: bool = False
ENABLE_SCHEMA_SUMMARIZATION: bool = False # generated once at profile-time, not at runtime
ENABLE_AMBIGUITY_DETECT: bool = True

# ── G2-04: Satisfaction Check ─────────────────────────────────────────────
SATISFACTION_CHECK_ENABLED: bool = True
SATISFACTION_CHECK_EXECUTION: bool = True
SATISFACTION_CHECK_PLAUSIBILITY: bool = True
SATISFACTION_CHECK_COLUMNS: bool = True
SATISFACTION_CHECK_SEMANTIC: bool = False # LLM-heavy, off by default
SATISFACTION_MIN_ROWS: int = 1
SATISFACTION_MAX_ROWS: int = 50_000
SATISFACTION_SEMANTIC_THRESHOLD: float = 0.75
SATISFACTION_MAX_FAILURES: int = 2 # escalate to HITL after this many check failures

# ── G2-05: Redis Schema Cache ─────────────────────────────────────────────
SCHEMA_CACHE_TTL: int = 600 # seconds — DDL content
PROFILE_CACHE_TTL: int = 1800 # seconds — table profile statistics
Comment on lines +69 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate cache TTLs as positive values.

These settings are unbounded, but CacheService.set() feeds them into Redis SETEX, which rejects ttl <= 0 and only logs the failure. A bad env var here quietly disables schema/profile caching for the whole service.

Suggested change
-    SCHEMA_CACHE_TTL: int = 600    # seconds — DDL content
-    PROFILE_CACHE_TTL: int = 1800  # seconds — table profile statistics
+    SCHEMA_CACHE_TTL: int = Field(default=600, gt=0)    # seconds — DDL content
+    PROFILE_CACHE_TTL: int = Field(default=1800, gt=0)  # seconds — table profile statistics
📝 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
# ── G2-05: Redis Schema Cache ─────────────────────────────────────────────
SCHEMA_CACHE_TTL: int = 600 # seconds — DDL content
PROFILE_CACHE_TTL: int = 1800 # seconds — table profile statistics
# ── G2-05: Redis Schema Cache ─────────────────────────────────────────────
SCHEMA_CACHE_TTL: int = Field(default=600, gt=0) # seconds — DDL content
PROFILE_CACHE_TTL: int = Field(default=1800, gt=0) # seconds — table profile statistics
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/src/agent/config.py` around lines 69 - 71, The SCHEMA_CACHE_TTL and
PROFILE_CACHE_TTL settings in Config are unvalidated, so invalid env values can
later cause CacheService.set() to pass non-positive TTLs into Redis SETEX. Add
positive-value validation to the Config fields (or equivalent startup config
validation) for these symbols so only integers greater than zero are accepted,
and ensure bad values fail fast instead of silently disabling caching.



settings = AgentSettings()
Loading