gaard-api 0.1.3__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. {gaard_api-0.1.3/src/gaard_api.egg-info → gaard_api-0.2.1}/PKG-INFO +5 -5
  2. {gaard_api-0.1.3 → gaard_api-0.2.1}/pyproject.toml +5 -5
  3. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/database.py +19 -3
  4. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/defaults.py +0 -67
  5. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/models.py +20 -0
  6. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/prompt_runtime.py +1 -46
  7. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/services.py +339 -258
  8. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin-web/assets/main.js +146 -48
  9. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin-web/assets/styles.css +9 -0
  10. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api/v1/admin.py +189 -107
  11. gaard_api-0.2.1/src/gaard_api/api/v1/analysis.py +1245 -0
  12. gaard_api-0.2.1/src/gaard_api/api/v1/query.py +1225 -0
  13. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/core/settings.py +11 -9
  14. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/main.py +3 -1
  15. {gaard_api-0.1.3 → gaard_api-0.2.1/src/gaard_api.egg-info}/PKG-INFO +5 -5
  16. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api.egg-info/SOURCES.txt +3 -2
  17. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api.egg-info/requires.txt +4 -4
  18. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_admin_api.py +854 -210
  19. gaard_api-0.2.1/tests/test_analysis_api.py +410 -0
  20. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_error_handlers.py +25 -0
  21. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_prompt_runtime.py +0 -31
  22. gaard_api-0.1.3/tests/test_investigation_error_messages.py → gaard_api-0.2.1/tests/test_query_error_messages.py +1 -1
  23. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_settings.py +1 -4
  24. gaard_api-0.1.3/src/gaard_api/admin-web/src/main.ts +0 -2753
  25. gaard_api-0.1.3/src/gaard_api/api/v1/query.py +0 -1113
  26. {gaard_api-0.1.3 → gaard_api-0.2.1}/MANIFEST.in +0 -0
  27. {gaard_api-0.1.3 → gaard_api-0.2.1}/README.md +0 -0
  28. {gaard_api-0.1.3 → gaard_api-0.2.1}/setup.cfg +0 -0
  29. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/__init__.py +0 -0
  30. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/__init__.py +0 -0
  31. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin/security.py +0 -0
  32. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin-web/index.html +0 -0
  33. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/admin-web/package.json +0 -0
  34. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api/__init__.py +0 -0
  35. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api/v1/__init__.py +0 -0
  36. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api/v1/prompts.py +0 -0
  37. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api/v1/schema.py +0 -0
  38. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/api_registry.py +0 -0
  39. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/cli.py +0 -0
  40. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/cli_commands.py +0 -0
  41. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/core/__init__.py +0 -0
  42. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/core/error_handlers.py +0 -0
  43. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/core/schema_cache.py +0 -0
  44. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/example_data/__init__.py +0 -0
  45. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
  46. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
  47. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
  48. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/example_database.py +0 -0
  49. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/extension_services.py +0 -0
  50. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/extensions.py +0 -0
  51. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api/server_cli.py +0 -0
  52. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api.egg-info/dependency_links.txt +0 -0
  53. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api.egg-info/entry_points.txt +0 -0
  54. {gaard_api-0.1.3 → gaard_api-0.2.1}/src/gaard_api.egg-info/top_level.txt +0 -0
  55. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_api_registry.py +0 -0
  56. {gaard_api-0.1.3 → gaard_api-0.2.1}/tests/test_server_cli.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaard-api
3
- Version: 0.1.3
3
+ Version: 0.2.1
4
4
  Summary: GAARD backend web services providing admin interface
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
7
- Requires-Dist: gaard-core==0.1.0
8
- Requires-Dist: gaard-connectors==0.1.1
9
- Requires-Dist: gaard-llm==0.1.0
10
- Requires-Dist: gaard-plugin-api<0.2.0,>=0.1.0
7
+ Requires-Dist: gaard-core==0.2.1
8
+ Requires-Dist: gaard-connectors==0.2.1
9
+ Requires-Dist: gaard-llm==0.2.1
10
+ Requires-Dist: gaard-plugin-api<0.3.0,>=0.2.0
11
11
  Requires-Dist: fastapi>=0.111.0
12
12
  Requires-Dist: uvicorn[standard]>=0.30.0
13
13
  Requires-Dist: pydantic>=2.7.0
@@ -4,16 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gaard-api"
7
- version = "0.1.3"
7
+ version = "0.2.1"
8
8
  description = "GAARD backend web services providing admin interface"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
 
12
12
  dependencies = [
13
- "gaard-core==0.1.0",
14
- "gaard-connectors==0.1.1",
15
- "gaard-llm==0.1.0",
16
- "gaard-plugin-api>=0.1.0,<0.2.0",
13
+ "gaard-core==0.2.1",
14
+ "gaard-connectors==0.2.1",
15
+ "gaard-llm==0.2.1",
16
+ "gaard-plugin-api>=0.2.0,<0.3.0",
17
17
  "fastapi>=0.111.0",
18
18
  "uvicorn[standard]>=0.30.0",
19
19
  "pydantic>=2.7.0",
@@ -1,7 +1,7 @@
1
1
  from collections.abc import Iterator
2
2
  import json
3
3
 
4
- from sqlalchemy import create_engine, select
4
+ from sqlalchemy import create_engine, delete, select
5
5
  from sqlalchemy.engine import Engine
6
6
  from sqlalchemy import inspect, text
7
7
  from sqlalchemy.orm import Session, sessionmaker
@@ -24,6 +24,7 @@ from gaard_api.core.settings import settings
24
24
  _engine: Engine | None = None
25
25
  _session_factory: sessionmaker[Session] | None = None
26
26
  _engine_url: str | None = None
27
+ LEGACY_PROMPT_KEYS = {"investigation_readiness"}
27
28
 
28
29
 
29
30
  def get_engine() -> Engine:
@@ -105,10 +106,12 @@ def seed_settings(session: Session) -> None:
105
106
  "gaard_sql_generation_mode": settings.gaard_sql_generation_mode,
106
107
  "gaard_result_interpretation_mode": settings.gaard_result_interpretation_mode,
107
108
  "gaard_output_classification_mode": settings.gaard_output_classification_mode,
108
- "gaard_investigation_mode": settings.gaard_investigation_mode,
109
- "gaard_investigation_ambiguity_mode": settings.gaard_investigation_ambiguity_mode,
110
109
  "gaard_query_max_rows": str(settings.gaard_query_max_rows),
111
110
  "gaard_query_timeout_seconds": str(settings.gaard_query_timeout_seconds),
111
+ "gaard_analysis_loop_count": str(settings.gaard_analysis_loop_count),
112
+ "gaard_analysis_auto_enable_business_logic": (
113
+ "true" if settings.gaard_analysis_auto_enable_business_logic else "false"
114
+ ),
112
115
  "gaard_llm_provider": settings.gaard_llm_provider,
113
116
  "gaard_llm_base_url": settings.gaard_llm_base_url,
114
117
  "gaard_llm_api_key": settings.gaard_llm_api_key,
@@ -154,6 +157,10 @@ def apply_runtime_settings(session: Session) -> None:
154
157
 
155
158
 
156
159
  def seed_prompts(session: Session) -> None:
160
+ session.execute(
161
+ delete(PromptTemplate).where(PromptTemplate.prompt_key.in_(LEGACY_PROMPT_KEYS))
162
+ )
163
+
157
164
  for prompt in DEFAULT_PROMPTS:
158
165
  existing = session.scalar(
159
166
  select(PromptTemplate).where(PromptTemplate.prompt_key == prompt["prompt_key"])
@@ -399,6 +406,15 @@ def ensure_data_query_audit_schema(engine: Engine) -> None:
399
406
  )
400
407
  )
401
408
 
409
+ if "llm_sql_language" not in columns:
410
+ with engine.begin() as connection:
411
+ connection.execute(
412
+ text(
413
+ "ALTER TABLE data_query_audit_logs "
414
+ "ADD COLUMN llm_sql_language VARCHAR(50) DEFAULT ''"
415
+ )
416
+ )
417
+
402
418
  with engine.begin() as connection:
403
419
  for index in DataQueryAuditLog.__table__.indexes:
404
420
  index.create(bind=connection, checkfirst=True)
@@ -86,66 +86,6 @@ Return one JSON object with:
86
86
  - reason: short explanation
87
87
  """
88
88
 
89
- DEFAULT_INVESTIGATION_READINESS_SYSTEM_PROMPT = """You are GAARD Investigation Readiness.
90
-
91
- Your task is to decide whether GAARD already knows enough to create a correct SQL query for the user's question.
92
-
93
- Assume nothing. Verify continuously.
94
-
95
- Use only:
96
- - the user's question,
97
- - the active datasource schema,
98
- - the approved or previously saved business logic supplied in the payload.
99
-
100
- You do not generate SQL.
101
- You do not answer the user.
102
- You decide only whether normal SQL generation may start safely.
103
-
104
- Return ready_for_sql=true only when all information needed for correct SQL is explicit in the question, schema, and business logic:
105
- - requested business entity or metric,
106
- - relevant tables, views and columns,
107
- - required filters and dictionary/status values,
108
- - required joins or relationships,
109
- - requested output shape such as count, list, detail, or aggregation.
110
-
111
- Return ready_for_sql=false when any material element is missing, ambiguous, inferred only from the model, or would require checking data values before SQL can be trusted. In that case route must be analysis.
112
-
113
- Output rules:
114
- - Return only a JSON object.
115
- - Do not include markdown.
116
- - Do not include reasoning outside the JSON.
117
- - Do not include <think> blocks.
118
- - Use exactly this JSON shape:
119
- {"ready_for_sql":false,"route":"analysis","confidence":0.0,"reason":"short reason","missing_information":[],"required_analysis":[],"required_analysis_tasks":[],"assumptions":[]}
120
-
121
- Required analysis task shape:
122
- {"missing_information":"what is missing","required_analysis":"specific read-only data question for SQL analysis","category":"dictionary_value","expected_output":"what kind of result would resolve this"}
123
-
124
- Allowed categories:
125
- - dictionary_value
126
- - relationship_logic
127
- - filter_logic
128
- - aggregation_logic
129
- - entity_mapping
130
- - unknown
131
- """
132
-
133
- DEFAULT_INVESTIGATION_READINESS_USER_PROMPT = """Assess whether normal SQL generation can start.
134
-
135
- Input JSON:
136
- {payload}
137
-
138
- Return one JSON object with:
139
- - ready_for_sql: boolean
140
- - route: sql or analysis
141
- - confidence: number from 0 to 1
142
- - reason: short explanation
143
- - missing_information: list of missing or ambiguous items
144
- - required_analysis: list of checks that Analysis mode should perform when ready_for_sql=false
145
- - required_analysis_tasks: list of structured SQL-analysis tasks with missing_information, required_analysis, category, expected_output
146
- - assumptions: list of any assumptions that would affect SQL correctness
147
- """
148
-
149
89
  DEFAULT_RESULT_INTERPRETATION_SYSTEM_PROMPT = """You are GAARD Data Result Interpreter.
150
90
 
151
91
  Your task is to explain SQL query results to the user.
@@ -247,13 +187,6 @@ DEFAULT_PROMPTS = [
247
187
  "system_prompt": DEFAULT_SQL_GENERATION_SYSTEM_PROMPT,
248
188
  "user_prompt_template": DEFAULT_SQL_GENERATION_USER_PROMPT,
249
189
  },
250
- {
251
- "prompt_key": "investigation_readiness",
252
- "name": "Investigation: readiness",
253
- "description": "Decides whether Investigation can safely delegate to normal SQL generation.",
254
- "system_prompt": DEFAULT_INVESTIGATION_READINESS_SYSTEM_PROMPT,
255
- "user_prompt_template": DEFAULT_INVESTIGATION_READINESS_USER_PROMPT,
256
- },
257
190
  {
258
191
  "prompt_key": "result_interpretation",
259
192
  "name": "Result interpretation",
@@ -89,6 +89,7 @@ class DataQueryAuditLog(Base):
89
89
  question: Mapped[str] = mapped_column(Text)
90
90
  answer: Mapped[str] = mapped_column(Text)
91
91
  sql: Mapped[str] = mapped_column(Text)
92
+ llm_sql_language: Mapped[str] = mapped_column(String(50), default="")
92
93
  output_classification: Mapped[OutputClassification] = mapped_column(
93
94
  SAEnum(
94
95
  OutputClassification,
@@ -251,3 +252,22 @@ class BusinessKnowledgeClaim(Base):
251
252
  onupdate=utc_now,
252
253
  )
253
254
  updated_by: Mapped[str] = mapped_column(String(255), default="system")
255
+
256
+
257
+ class AnalysisSessionRecord(Base):
258
+ __tablename__ = "analysis_sessions"
259
+
260
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
261
+ session_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
262
+ status: Mapped[str] = mapped_column(String(50), index=True, default="running")
263
+ user_id: Mapped[str] = mapped_column(String(255), index=True)
264
+ datasource_id: Mapped[str] = mapped_column(String(255), index=True)
265
+ question: Mapped[str] = mapped_column(Text)
266
+ context_json: Mapped[str] = mapped_column(Text, default="{}")
267
+ events_json: Mapped[str] = mapped_column(Text, default="[]")
268
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
269
+ updated_at: Mapped[datetime] = mapped_column(
270
+ DateTime(timezone=True),
271
+ default=utc_now,
272
+ onupdate=utc_now,
273
+ )
@@ -1,5 +1,4 @@
1
1
  from gaard_core.errors import ConfigurationError
2
- from gaard_core.investigation.models import InvestigationContext
3
2
  from gaard_core.json_utils import json_dumps
4
3
  from gaard_core.prompt_compiler.models import CompiledPrompt, SqlGenerationPromptRequest
5
4
  from gaard_core.prompt_compiler.schema_formatter import SchemaPromptFormatter
@@ -54,9 +53,7 @@ class MetadataSqlGenerationPromptCompiler:
54
53
  return request.formatted_schema
55
54
 
56
55
  if request.database_schema is None:
57
- raise ConfigurationError(
58
- "Either database_schema or formatted_schema must be provided."
59
- )
56
+ raise ConfigurationError("Either database_schema or formatted_schema must be provided.")
60
57
 
61
58
  return self.schema_formatter.format(request.database_schema)
62
59
 
@@ -88,37 +85,6 @@ class MetadataIntentClassificationPromptCompiler:
88
85
  )
89
86
 
90
87
 
91
- class MetadataInvestigationReadinessPromptCompiler:
92
- def __init__(self, prompt_template: PromptTemplate) -> None:
93
- self.prompt_template = prompt_template
94
-
95
- def compile(self, context: InvestigationContext) -> CompiledPrompt:
96
- payload = {
97
- "question": context.question,
98
- "datasource_id": context.datasource_id,
99
- "user_id": context.user_id,
100
- "schema": context.formatted_schema,
101
- "business_logic": context.business_logic,
102
- }
103
- payload_json = json_dumps(payload, ensure_ascii=False, indent=2)
104
-
105
- return CompiledPrompt(
106
- system_prompt=self.prompt_template.system_prompt,
107
- user_prompt=self.prompt_template.user_prompt_template.format(
108
- payload=payload_json,
109
- question=context.question,
110
- datasource_id=context.datasource_id,
111
- user_id=context.user_id,
112
- schema=context.formatted_schema,
113
- business_logic=context.business_logic,
114
- ),
115
- metadata={
116
- "prompt_key": self.prompt_template.prompt_key,
117
- "prompt_version": self.prompt_template.version,
118
- },
119
- )
120
-
121
-
122
88
  class MetadataResultInterpretationPromptCompiler:
123
89
  def __init__(self, prompt_template: PromptTemplate) -> None:
124
90
  self.prompt_template = prompt_template
@@ -204,17 +170,6 @@ def get_intent_classification_prompt_compiler() -> (
204
170
  return MetadataIntentClassificationPromptCompiler(prompt_template=prompt_template)
205
171
 
206
172
 
207
- def get_investigation_readiness_prompt_compiler() -> (
208
- MetadataInvestigationReadinessPromptCompiler | None
209
- ):
210
- prompt_template = get_active_prompt_template_safe("investigation_readiness")
211
-
212
- if prompt_template is None:
213
- return None
214
-
215
- return MetadataInvestigationReadinessPromptCompiler(prompt_template=prompt_template)
216
-
217
-
218
173
  def get_result_interpretation_prompt_compiler() -> (
219
174
  MetadataResultInterpretationPromptCompiler | None
220
175
  ):