-
Notifications
You must be signed in to change notification settings - Fork 0
add sso and use util package #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
235d321
efa52bf
fdb8f4f
e4abbda
281d619
a84a60e
5f80832
17a3e00
cd5cb38
eb2d3d5
ad4a6a0
56c6353
33c6384
1209e55
faf65eb
785cd17
42b24a8
5360e40
1837461
d36844f
b6a0453
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| .venv | ||
| **/__pycache__ | ||
| *.pyc | ||
| .git | ||
| .pytest_cache | ||
| node_modules | ||
| dist | ||
| .env | ||
| .env.local |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| FROM python:3.12-slim | ||
|
|
||
| RUN pip install uv | ||
| RUN apt-get update && apt-get install -y git openssh-client | ||
|
Comment on lines
+1
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a non-root user and optimize apt-get. Running as root in the container is a security risk. Also, add 🔧 Proposed fix FROM python:3.12-slim
+RUN useradd -m -s /bin/bash agent
+
RUN pip install uv
-RUN apt-get update && apt-get install -y git openssh-client
+RUN apt-get update && apt-get install -y --no-install-recommends git openssh-client \
+ && rm -rf /var/lib/apt/lists/*Then at the end of the Dockerfile, before CMD: +USER agent
CMD ["uvicorn", "agent.main:app", "--host", "0.0.0.0", "--port", "8001"]Note: You may need to adjust the SSH key mount path and 🧰 Tools🪛 Trivy (0.69.3)[error] 1-1: Image user should not be 'root' Specify at least 1 USER command in Dockerfile with non-root user as argument Rule: DS-0002 (IaC/Dockerfile) [error] 4-4: 'apt-get' missing '--no-install-recommends' '--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y git openssh-client' Rule: DS-0029 (IaC/Dockerfile) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| # Configure git to use ssh for github.com | ||
| RUN git config --global url."git@github.com:".insteadOf "https://github.com/" | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Copy workspace | ||
| COPY core /app/core | ||
| COPY agent /app/agent | ||
|
|
||
| WORKDIR /app/agent | ||
|
|
||
| # Mount SSH deploy key secret and sync dependencies | ||
| RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 \ | ||
| mkdir -p -m 0700 /root/.ssh && \ | ||
| ssh-keyscan github.com >> /root/.ssh/known_hosts && \ | ||
| uv sync | ||
|
|
||
| # 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"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| [project] | ||
| name = "agent" | ||
| version = "0.1.0" | ||
| description = "Text2SQL Agent Service" | ||
| readme = "README.md" | ||
| requires-python = ">=3.12,<3.13" | ||
| dependencies = [ | ||
| "fastapi>=0.115.6", | ||
| "uvicorn[standard]>=0.32.1", | ||
| "langgraph", | ||
| "langchain-ollama", | ||
| "core", | ||
| "esca-sdk", | ||
| "pydantic-settings>=2.7.0", | ||
| "trino>=0.328.0", | ||
| "langfuse>=2.0.0", | ||
| "langchain", | ||
| "langchain-openai" | ||
| ] | ||
|
|
||
| [tool.uv.sources] | ||
| core = { path = "../core" } | ||
| esca-sdk = { git = "https://github.com/elirazpevz/esca.git", subdirectory = "packages/esca-sdk" } | ||
|
|
||
| [build-system] | ||
| requires = ["uv_build>=0.11.7,<0.12.0"] | ||
| build-backend = "uv_build" | ||
|
|
||
| [dependency-groups] | ||
| dev = [ | ||
| "pytest>=9.0.3", | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,188 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from langfuse import Langfuse | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ============================================================================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # CONFIGURATION - Set your private Langfuse server credentials here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ============================================================================== | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LANGFUSE_PUBLIC_KEY = "pk-lf-..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LANGFUSE_SECRET_KEY = "sk-lf-..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LANGFUSE_BASE_URL = "http://localhost:3000" # Replace with your Langfuse URL | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Connecting to Langfuse at {LANGFUSE_BASE_URL}...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| langfuse_client = Langfuse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public_key=LANGFUSE_PUBLIC_KEY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret_key=LANGFUSE_SECRET_KEY, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| host=LANGFUSE_BASE_URL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompts_to_upload = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/extractor", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are a query enrichment assistant for a text-to-SQL system.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Your job is to read the user's natural-language query and add context that " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "makes ambiguous or implicit terms clearer for downstream processing.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Add enrichment entries for things like:\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " • Abbreviations or acronyms that have a specific meaning " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "(e.g. 'MDA' → 'Magen David Adom')\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " • Relative time expressions that can be resolved to absolute dates " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "(e.g. 'last quarter' → 'Q1 2025, Jan 1 – Mar 31 2025')\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " • Ambiguous proper nouns where context helps " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "(e.g. 'Jordan' used as a country vs. a person's name)\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| " • Domain-specific shorthand the downstream system may not know\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Do NOT try to identify which database table or column to use — that is handled by a " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "separate schema exploration phase.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Do NOT add enrichments for terms that are already fully clear from the query.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "If the query needs no enrichment, return an empty enrichments list." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "{{user_query}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/schema_explorer", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are a Schema Explorer sub-agent. Your goal is to identify the most relevant tables " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "and inspect their column details to form a query plan for the user's question.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Candidate Tables found:\n{{tables_json}}\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Detailed Profiles for top tables (with Esca Reference IDs):\n{{profiles_json}}\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "## Decision-Making Rules\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You MUST make all planning decisions autonomously. This includes:\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- Join strategy: If multiple tables are needed to answer the query, decide which tables to join and on which keys — do NOT ask the user.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- Column selection: Choose the most appropriate columns yourself.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- Filter strategy: Infer filters from the user's question.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- Table selection when one is clearly more appropriate: Pick the best match and proceed.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "## When to Flag Ambiguity (ONLY these cases)\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Set ambiguity_detected=true ONLY IF:\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "1. Two or more completely independent tables could each independently and fully answer the user's question, " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "and you have no way to determine which one the user wants (e.g., 'orders' vs 'orders_archive' with no " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "indication of time range, or two fact tables from different business domains that both seem equally relevant).\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "2. There is no table in the catalog that can answer the query at all.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Do NOT flag ambiguity for: join decisions, column choices, filter logic, or any decision you can make yourself.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "## Output Format\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Output MUST be a valid JSON object with the following keys:\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- schema_plan: detailed query plan describing tables, columns, joins, and Esca reference IDs (empty string if ambiguity_detected is true)\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- ambiguity_detected: boolean, true ONLY in the hard-blocker cases described above\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- ambiguity_message: a concise question to ask the user to resolve the blocker (empty string if ambiguity_detected is false)\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- candidate_options: list of strings (table names or options) for the user to choose from (empty list if ambiguity_detected is false)\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Return only the raw JSON, no markdown formatting (no ```json code blocks)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "{{human_message}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/query_builder", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "You are a SQL expert who specializes in trino. Build a SQL query based on the plan and user query. Output ONLY the SQL query, nothing else." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "Plan: {{schema_plan}}\nQuery: {{user_query}}{{feedback_str}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/refiner", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "You are a Trino SQL expert. Fix the SQL query based on the database error. Output ONLY the fixed SQL query, nothing else (no backticks, no explanation)." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "SQL: {{sql}}\nError: {{error}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/finalizer_summary", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are a helpful data assistant. Summarize the findings for the user nicely. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are given the SQL query that was executed and a preview of the queried data (columns and first few rows) to help you understand the context of the results. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Note: If the columns contain single items or aliases like `_col0` with a numeric value, this is the result of an aggregation query (such as `COUNT(*)`). Use this direct result to answer the user's question.\n\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Data Preview:\n{{data_preview}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "User asked: {{user_query}}\nSQL Query: {{sql_query}}\nData Ref: {{raw_data_ref}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/finalizer_sql_explanation", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are a database analyst assistant. Explain the following SQL query in clear, natural language. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Describe what fields and tables are queried, any filters, joins, groupings, or aggregations, and what the query accomplishes. " | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Keep the explanation concise and professional." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "SQL Query:\n{{sql_query}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": "text2sql/rejection_router", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "prompt": [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "system", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "You are a sub-agent routing system. Analyze the user feedback on the previous query builder plan and select which phase to route the execution back to:\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- extractor: Choose this if the user is correcting the query intent, entities, constants, adding or mentioning new entities or asking a completely different question.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- schema_explorer: Choose this if the user points out a wrong table, suggests using a different table/view, or references table schema selection issues.\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "- query_builder: Choose this if the user is requesting a minor change in the SQL query itself (like modifying a WHERE clause, sorting, limits, joins, or specific syntax adjustments), but the tables and overall query intent are correct." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "role": "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "content": "User Feedback: {{feedback}}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "type": "chat" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for p in prompts_to_upload: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Uploading prompt: {p['name']}...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| langfuse_client.create_prompt( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name=p["name"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt=p["prompt"], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type=p["type"] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f" ✓ Successfully uploaded {p['name']}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f" ✗ Failed to upload {p['name']}: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+175
to
+185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail the script when any prompt upload fails. Lines 184-185 swallow errors and continue, so the script can exit successfully with a partially uploaded prompt set. Suggested fix- for p in prompts_to_upload:
+ failed = []
+ for p in prompts_to_upload:
try:
print(f"Uploading prompt: {p['name']}...")
langfuse_client.create_prompt(
name=p["name"],
prompt=p["prompt"],
type=p["type"]
)
print(f" ✓ Successfully uploaded {p['name']}")
except Exception as e:
print(f" ✗ Failed to upload {p['name']}: {e}")
+ failed.append(p["name"])
+
+ if failed:
+ raise SystemExit(f"Upload failed for prompts: {', '.join(failed)}")📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.15)[warning] 184-184: Do not catch blind exception: (BLE001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Agent package |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from pydantic import Field | ||
| from pydantic_settings import BaseSettings, SettingsConfigDict | ||
|
|
||
| class AgentSettings(BaseSettings): | ||
| model_config = SettingsConfigDict(env_file=".env", extra="ignore") | ||
|
|
||
| ESCA_API_KEY: str = "" | ||
| ESCA_URL: str = "http://localhost:7010" | ||
| LLM_API_KEY: str = "ollama" | ||
| LLM_BASE_URL: str = "http://localhost:11434/v1" | ||
| LLM_MODEL: str = "gemma4:e4b" | ||
| EMBEDDER_URL: str = "http://localhost:11434" | ||
| EMBEDDER_MODEL: str = "nomic-embed-text:latest" | ||
| HYBRID_SEARCH_MAX_TABLES: int = 10 | ||
| MAX_PROFILES_TO_FETCH: int = 3 | ||
|
|
||
| LANGFUSE_SECRET_KEY: str = Field(min_length=1) | ||
| LANGFUSE_PUBLIC_KEY: str = Field(min_length=1) | ||
| LANGFUSE_BASE_URL: str = Field(min_length=1) | ||
|
|
||
| # 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_REJECTION_ROUTER: str = "text2sql/rejection_router" | ||
|
|
||
|
|
||
| settings = AgentSettings() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| from langgraph.graph import StateGraph, START, END | ||
| from langgraph.checkpoint.memory import MemorySaver | ||
| from langchain_openai import ChatOpenAI | ||
| from langchain_core.prompts import ChatPromptTemplate | ||
| from agent.state import AgentState | ||
| from agent.nodes.extractor import extractor_node | ||
| from agent.nodes.schema_explorer import schema_explorer_node | ||
| from agent.nodes.query_builder import query_builder_node | ||
| from agent.nodes.refiner import refiner_node | ||
| from agent.nodes.finalizer import finalizer_node | ||
| from agent.config import settings | ||
| from agent.langfuse_client import langfuse_client | ||
| from pydantic import BaseModel, Field | ||
| from typing import Literal | ||
|
|
||
| # Initialize LLM for rejection routing classification | ||
| llm = ChatOpenAI(model=settings.LLM_MODEL, base_url=settings.LLM_BASE_URL, api_key=settings.LLM_API_KEY, temperature=0) | ||
|
|
||
| class RejectionRoute(BaseModel): | ||
| route: Literal["extractor", "schema_explorer", "query_builder"] = Field( | ||
| description="The phase to route the execution back to based on the user feedback." | ||
| ) | ||
|
|
||
| def rejection_router_node(state: AgentState): | ||
| """Classify user rejection feedback and select the appropriate phase to return to.""" | ||
| feedback = state.get("feedback") | ||
| if not feedback: | ||
| return {"feedback_route": "query_builder"} | ||
|
|
||
| langfuse_prompt = langfuse_client.get_prompt(settings.LANGFUSE_PROMPT_REJECTION_ROUTER) | ||
| prompt = ChatPromptTemplate.from_messages(langfuse_prompt.get_langchain_prompt()) | ||
|
|
||
| structured_llm = llm.with_structured_output(RejectionRoute, method="json_schema") | ||
| chain = prompt | structured_llm | ||
| try: | ||
| response = chain.invoke({"feedback": feedback}) | ||
| route = response.route | ||
| except Exception as e: | ||
| print(f"Structured output parsing failed: {e}") | ||
| route = "extractor" # Fallback | ||
|
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: head -50 agent/src/agent/graph.py | cat -nRepository: StavPonte11/text2sql-onboarding Length of output: 2478 🏁 Script executed: rg -n "rejection_router_node|rejection_router|except Exception" agent/src/agent/graph.py -A 3 -B 3Repository: StavPonte11/text2sql-onboarding Length of output: 1749 🏁 Script executed: wc -l agent/src/agent/graph.pyRepository: StavPonte11/text2sql-onboarding Length of output: 103 Do not silently swallow all rejection-routing failures. Lines 38–40 catch all exceptions from the LLM chain invocation and unconditionally fall back to the "extractor" route. This masks real errors (LLM failures, prompt issues, parsing errors, API faults) and silently causes incorrect control flow—user feedback is ignored without notification. The 🧰 Tools🪛 Ruff (0.15.15)[warning] 38-38: Do not catch blind exception: (BLE001) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| return {"feedback_route": route} | ||
|
|
||
|
|
||
| def route_refiner(state: AgentState): | ||
| if state.get("trino_error") and state.get("refinement_count", 0) < 3: | ||
| return "refiner" | ||
| return "finalizer" | ||
|
|
||
| def route_query_builder(state: AgentState): | ||
| if state.get("feedback"): | ||
| return "rejection_router" | ||
| return "refiner" | ||
|
|
||
| def route_rejection(state: AgentState): | ||
| route = state.get("feedback_route") | ||
| if route in ["extractor", "schema_explorer", "query_builder"]: | ||
| return route | ||
| return "extractor" | ||
|
|
||
| workflow = StateGraph(AgentState) | ||
|
|
||
| workflow.add_node("extractor", extractor_node) | ||
| workflow.add_node("schema_explorer", schema_explorer_node) | ||
| workflow.add_node("query_builder", query_builder_node) | ||
| workflow.add_node("rejection_router", rejection_router_node) | ||
| workflow.add_node("refiner", refiner_node) | ||
| workflow.add_node("finalizer", finalizer_node) | ||
|
|
||
| workflow.add_edge(START, "extractor") | ||
| workflow.add_edge("extractor", "schema_explorer") | ||
| workflow.add_edge("schema_explorer", "query_builder") | ||
|
|
||
| workflow.add_conditional_edges("query_builder", route_query_builder, {"rejection_router": "rejection_router", "refiner": "refiner"}) | ||
| workflow.add_conditional_edges("rejection_router", route_rejection, {"extractor": "extractor", "schema_explorer": "schema_explorer", "query_builder": "query_builder"}) | ||
|
|
||
| workflow.add_conditional_edges("refiner", route_refiner, {"refiner": "refiner", "finalizer": "finalizer"}) | ||
| workflow.add_edge("finalizer", END) | ||
|
|
||
| memory = MemorySaver() | ||
| agent_graph = workflow.compile(checkpointer=memory) | ||
|
Comment on lines
+80
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's examine the file and surrounding context
cat -n agent/src/agent/graph.py | head -100 | tail -30Repository: StavPonte11/text2sql-onboarding Length of output: 1512 🏁 Script executed: # Check the imports to understand which MemorySaver is being used
head -30 agent/src/agent/graph.pyRepository: StavPonte11/text2sql-onboarding Length of output: 1447 🌐 Web query:
💡 Result: LangGraph's checkpointer durability determines how the system persists graph state snapshots and handles execution flow [1]. Key durability and storage considerations include: 1. Storage Persistence: - InMemorySaver: Designed exclusively for development, testing, and debugging [2][1]. It is ephemeral and will not survive process restarts, crashes, or multi-replica deployments [2][3]. - Production Backends: For production, you must use a database-backed checkpointer such as PostgresSaver (using PostgreSQL) [4][5][1]. These implementations support persistent storage, allowing threads to resume after interruptions or failures [6][7][1]. 2. Durability Modes: - sync (Default): Checkpoints the state before each superstep [1]. This mode provides inherent backpressure because the graph loop awaits the completion of each checkpoint write before proceeding to the next step, ensuring higher consistency and reliability at the cost of slight latency [8]. - async: Attempts to checkpoint asynchronously [1]. While this can improve performance by not blocking the main execution loop, it lacks backpressure [8]. Under high load or with slow write latency, this may lead to memory accumulation (e.g., coroutine chains) if the system generates state faster than it can persist it [8]. 3. Production Best Practices: - Always use connection pooling (e.g., via Citations:
Line 80 uses an in-memory checkpointer designed exclusively for development and testing. It does not persist state across process restarts, crashes, or multiple service instances, causing thread resumption to fail unpredictably in production. Use a database-backed checkpointer like 🤖 Prompt for AI Agents |
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| from langfuse import Langfuse | ||
| from agent.config import settings | ||
|
|
||
| langfuse_client = Langfuse( | ||
| public_key=settings.LANGFUSE_PUBLIC_KEY, | ||
| secret_key=settings.LANGFUSE_SECRET_KEY, | ||
| host=settings.LANGFUSE_BASE_URL, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overly broad ignore pattern may hide legitimate config files.
The
*.confpattern ignores all.conffiles throughout the repository, which may inadvertently exclude configuration files that should be version-controlled. Consider using a more specific pattern likesnowflake*.confor placing this rule in a subdirectory-specific.gitignoreif the intent is to ignore only local Snowflake configuration files.📝 Suggested refinement
🤖 Prompt for AI Agents