gaard-core 0.1.0__tar.gz → 0.2.0__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.
- {gaard_core-0.1.0 → gaard_core-0.2.0}/PKG-INFO +2 -1
- {gaard_core-0.1.0 → gaard_core-0.2.0}/pyproject.toml +2 -1
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_pipeline/models.py +1 -6
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_pipeline/pipeline.py +20 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core.egg-info/PKG-INFO +2 -1
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core.egg-info/SOURCES.txt +0 -12
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core.egg-info/requires.txt +1 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_query_pipeline.py +24 -0
- gaard_core-0.1.0/src/gaard_core/investigation/__init__.py +0 -25
- gaard_core-0.1.0/src/gaard_core/investigation/llm_readiness_agent.py +0 -220
- gaard_core-0.1.0/src/gaard_core/investigation/loop.py +0 -83
- gaard_core-0.1.0/src/gaard_core/investigation/mock_readiness_agent.py +0 -20
- gaard_core-0.1.0/src/gaard_core/investigation/models.py +0 -62
- gaard_core-0.1.0/src/gaard_core/prompt_compiler/investigation_readiness_prompt.py +0 -84
- gaard_core-0.1.0/src/gaard_core/result_interpreter/__init__.py +0 -0
- gaard_core-0.1.0/src/gaard_core/schema/__init__.py +0 -0
- gaard_core-0.1.0/src/gaard_core/security/__init__.py +0 -0
- gaard_core-0.1.0/src/gaard_core/semantic_layer/__init__.py +0 -0
- gaard_core-0.1.0/src/gaard_core/sql_validator/__init__.py +0 -0
- gaard_core-0.1.0/tests/test_investigation_readiness.py +0 -150
- {gaard_core-0.1.0 → gaard_core-0.2.0}/README.md +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/setup.cfg +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/errors.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/audit → gaard_core-0.2.0/src/gaard_core/execution}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/execution/mock_executor.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/json_utils.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/llm_output.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/evaluation → gaard_core-0.2.0/src/gaard_core/prompt_compiler}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/intent_classification_prompt.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/models.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/result_classification_prompt.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/result_interpretation_prompt.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/schema_formatter.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/sql_generation_prompt.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_intent/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_intent/llm_classifier.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_intent/mock_classifier.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/execution → gaard_core-0.2.0/src/gaard_core/query_pipeline}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_pipeline/llm_sql_generator.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/query_pipeline/mock_sql_generator.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/result_classifier/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/result_classifier/llm_classifier.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/result_classifier/mock_classifier.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/policy_engine → gaard_core-0.2.0/src/gaard_core/result_interpreter}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/result_interpreter/llm_interpreter.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/result_interpreter/mock_interpreter.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/prompt_compiler → gaard_core-0.2.0/src/gaard_core/schema}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/schema/cache.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/schema/context.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/schema/models.py +0 -0
- {gaard_core-0.1.0/src/gaard_core/query_pipeline → gaard_core-0.2.0/src/gaard_core/sql_validator}/__init__.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/sql_validator/select_only.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core.egg-info/dependency_links.txt +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core.egg-info/top_level.txt +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_json_utils.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_llm_output.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_llm_query_intent_classifier.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_llm_result_classifier.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_llm_result_interpreter.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_llm_sql_generator.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_result_classification_prompt_compiler.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_result_interpretation_prompt_compiler.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_schema_context_cache.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_schema_context_service.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_schema_prompt_formatter.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_sql_generation_prompt_compiler.py +0 -0
- {gaard_core-0.1.0 → gaard_core-0.2.0}/tests/test_sql_validator.py +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gaard-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Core GAARD query pipeline, prompt compiler, policies and SQL validation
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: gaard-plugin-api<0.3.0,>=0.2.0
|
|
7
8
|
Requires-Dist: pydantic>=2.7.0
|
|
8
9
|
Requires-Dist: sqlglot>=25.0.0
|
|
9
10
|
Provides-Extra: dev
|
|
@@ -4,11 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gaard-core"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Core GAARD query pipeline, prompt compiler, policies and SQL validation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
11
11
|
dependencies = [
|
|
12
|
+
"gaard-plugin-api>=0.2.0,<0.3.0",
|
|
12
13
|
"pydantic>=2.7.0",
|
|
13
14
|
"sqlglot>=25.0.0",
|
|
14
15
|
]
|
|
@@ -19,11 +19,6 @@ class QueryIntentDecision(StrEnum):
|
|
|
19
19
|
AMBIGUOUS = "ambiguous"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class QueryMode(StrEnum):
|
|
23
|
-
SQL = "sql"
|
|
24
|
-
INVESTIGATION = "investigation"
|
|
25
|
-
|
|
26
|
-
|
|
27
22
|
class QueryIntentClassification(BaseModel):
|
|
28
23
|
decision: QueryIntentDecision = QueryIntentDecision.AMBIGUOUS
|
|
29
24
|
confidence: float = 0.0
|
|
@@ -35,7 +30,7 @@ class QueryRequest(BaseModel):
|
|
|
35
30
|
question: str = Field(min_length=1)
|
|
36
31
|
datasource_id: str = "default"
|
|
37
32
|
user_id: str = "local-admin"
|
|
38
|
-
|
|
33
|
+
interpret: bool = True
|
|
39
34
|
|
|
40
35
|
|
|
41
36
|
class GeneratedSql(BaseModel):
|
|
@@ -73,6 +73,26 @@ class QueryPipeline:
|
|
|
73
73
|
self.sql_validator.validate(generated_sql.sql)
|
|
74
74
|
|
|
75
75
|
result = self.executor.execute(generated_sql.sql)
|
|
76
|
+
if not request.interpret:
|
|
77
|
+
duration_ms = round((time.perf_counter() - started_at) * 1000, 2)
|
|
78
|
+
return QueryResponse(
|
|
79
|
+
question=request.question,
|
|
80
|
+
answer="",
|
|
81
|
+
sql=generated_sql.sql,
|
|
82
|
+
rows=result.rows,
|
|
83
|
+
metadata={
|
|
84
|
+
"duration_ms": duration_ms,
|
|
85
|
+
"datasource_id": request.datasource_id,
|
|
86
|
+
"user_id": request.user_id,
|
|
87
|
+
"confidence": generated_sql.confidence,
|
|
88
|
+
"assumptions": generated_sql.assumptions,
|
|
89
|
+
"sql_generation_mode": self.sql_generation_mode,
|
|
90
|
+
"result_interpretation_mode": "none",
|
|
91
|
+
"output_classification_mode": "none",
|
|
92
|
+
"output_classification": OutputClassification.UNKNOWN.value,
|
|
93
|
+
"raw_sql_output": True,
|
|
94
|
+
},
|
|
95
|
+
)
|
|
76
96
|
|
|
77
97
|
try:
|
|
78
98
|
answer = self.interpreter.interpret(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gaard-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Core GAARD query pipeline, prompt compiler, policies and SQL validation
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: gaard-plugin-api<0.3.0,>=0.2.0
|
|
7
8
|
Requires-Dist: pydantic>=2.7.0
|
|
8
9
|
Requires-Dist: sqlglot>=25.0.0
|
|
9
10
|
Provides-Extra: dev
|
|
@@ -9,19 +9,10 @@ src/gaard_core.egg-info/SOURCES.txt
|
|
|
9
9
|
src/gaard_core.egg-info/dependency_links.txt
|
|
10
10
|
src/gaard_core.egg-info/requires.txt
|
|
11
11
|
src/gaard_core.egg-info/top_level.txt
|
|
12
|
-
src/gaard_core/audit/__init__.py
|
|
13
|
-
src/gaard_core/evaluation/__init__.py
|
|
14
12
|
src/gaard_core/execution/__init__.py
|
|
15
13
|
src/gaard_core/execution/mock_executor.py
|
|
16
|
-
src/gaard_core/investigation/__init__.py
|
|
17
|
-
src/gaard_core/investigation/llm_readiness_agent.py
|
|
18
|
-
src/gaard_core/investigation/loop.py
|
|
19
|
-
src/gaard_core/investigation/mock_readiness_agent.py
|
|
20
|
-
src/gaard_core/investigation/models.py
|
|
21
|
-
src/gaard_core/policy_engine/__init__.py
|
|
22
14
|
src/gaard_core/prompt_compiler/__init__.py
|
|
23
15
|
src/gaard_core/prompt_compiler/intent_classification_prompt.py
|
|
24
|
-
src/gaard_core/prompt_compiler/investigation_readiness_prompt.py
|
|
25
16
|
src/gaard_core/prompt_compiler/models.py
|
|
26
17
|
src/gaard_core/prompt_compiler/result_classification_prompt.py
|
|
27
18
|
src/gaard_core/prompt_compiler/result_interpretation_prompt.py
|
|
@@ -45,11 +36,8 @@ src/gaard_core/schema/__init__.py
|
|
|
45
36
|
src/gaard_core/schema/cache.py
|
|
46
37
|
src/gaard_core/schema/context.py
|
|
47
38
|
src/gaard_core/schema/models.py
|
|
48
|
-
src/gaard_core/security/__init__.py
|
|
49
|
-
src/gaard_core/semantic_layer/__init__.py
|
|
50
39
|
src/gaard_core/sql_validator/__init__.py
|
|
51
40
|
src/gaard_core/sql_validator/select_only.py
|
|
52
|
-
tests/test_investigation_readiness.py
|
|
53
41
|
tests/test_json_utils.py
|
|
54
42
|
tests/test_llm_output.py
|
|
55
43
|
tests/test_llm_query_intent_classifier.py
|
|
@@ -45,6 +45,11 @@ class FailingInterpreter:
|
|
|
45
45
|
raise LlmProviderError("LLM provider request failed.")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class FailingClassifier:
|
|
49
|
+
def classify(self, request: QueryRequest, answer: str):
|
|
50
|
+
raise AssertionError("classifier should not be called")
|
|
51
|
+
|
|
52
|
+
|
|
48
53
|
def test_query_pipeline_wraps_llm_provider_error_during_sql_generation() -> None:
|
|
49
54
|
pipeline = QueryPipeline(sql_generator=FailingSqlGenerator())
|
|
50
55
|
|
|
@@ -69,3 +74,22 @@ def test_query_pipeline_wraps_llm_provider_error_during_result_interpretation()
|
|
|
69
74
|
assert exc_info.value.code == "LLM_PROVIDER_ERROR"
|
|
70
75
|
assert exc_info.value.phase == "result_interpretation"
|
|
71
76
|
assert exc_info.value.sql == "SELECT 1 AS value"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_query_pipeline_can_return_raw_sql_output_without_interpretation() -> None:
|
|
80
|
+
pipeline = QueryPipeline(
|
|
81
|
+
sql_generator=StaticSqlGenerator(),
|
|
82
|
+
executor=StaticExecutor(),
|
|
83
|
+
interpreter=FailingInterpreter(),
|
|
84
|
+
classifier=FailingClassifier(),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
response = pipeline.handle(QueryRequest(question="Inspect raw value", interpret=False))
|
|
88
|
+
|
|
89
|
+
assert response.answer == ""
|
|
90
|
+
assert response.sql == "SELECT 1 AS value"
|
|
91
|
+
assert response.rows == [{"value": 1}]
|
|
92
|
+
assert response.metadata["result_interpretation_mode"] == "none"
|
|
93
|
+
assert response.metadata["output_classification_mode"] == "none"
|
|
94
|
+
assert response.metadata["output_classification"] == "unknown"
|
|
95
|
+
assert response.metadata["raw_sql_output"] is True
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from gaard_core.investigation.loop import InvestigationLoop
|
|
2
|
-
from gaard_core.investigation.llm_readiness_agent import LlmInvestigationReadinessAgent
|
|
3
|
-
from gaard_core.investigation.mock_readiness_agent import MockInvestigationReadinessAgent
|
|
4
|
-
from gaard_core.investigation.models import (
|
|
5
|
-
InvestigationContext,
|
|
6
|
-
InvestigationIteration,
|
|
7
|
-
InvestigationLoopConfig,
|
|
8
|
-
InvestigationLoopResult,
|
|
9
|
-
InvestigationReadinessDecision,
|
|
10
|
-
InvestigationRoute,
|
|
11
|
-
RequiredAnalysisTask,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"InvestigationContext",
|
|
16
|
-
"InvestigationIteration",
|
|
17
|
-
"InvestigationLoop",
|
|
18
|
-
"InvestigationLoopConfig",
|
|
19
|
-
"InvestigationLoopResult",
|
|
20
|
-
"InvestigationReadinessDecision",
|
|
21
|
-
"InvestigationRoute",
|
|
22
|
-
"LlmInvestigationReadinessAgent",
|
|
23
|
-
"MockInvestigationReadinessAgent",
|
|
24
|
-
"RequiredAnalysisTask",
|
|
25
|
-
]
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Any, Protocol
|
|
3
|
-
|
|
4
|
-
from gaard_core.investigation.models import (
|
|
5
|
-
InvestigationContext,
|
|
6
|
-
InvestigationReadinessDecision,
|
|
7
|
-
InvestigationRoute,
|
|
8
|
-
RequiredAnalysisTask,
|
|
9
|
-
)
|
|
10
|
-
from gaard_core.llm_output import remove_thinking_blocks
|
|
11
|
-
from gaard_core.prompt_compiler.investigation_readiness_prompt import (
|
|
12
|
-
InvestigationReadinessPromptCompiler,
|
|
13
|
-
)
|
|
14
|
-
from gaard_core.prompt_compiler.models import CompiledPrompt
|
|
15
|
-
from gaard_llm.openai_compatible.client import OpenAICompatibleClient
|
|
16
|
-
from gaard_llm.providers.models import ChatCompletionRequest, ChatMessage
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class InvestigationReadinessPromptCompilerProtocol(Protocol):
|
|
20
|
-
def compile(self, context: InvestigationContext) -> CompiledPrompt:
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class LlmInvestigationReadinessAgent:
|
|
25
|
-
name = "llm_investigation_readiness"
|
|
26
|
-
|
|
27
|
-
def __init__(
|
|
28
|
-
self,
|
|
29
|
-
client: OpenAICompatibleClient,
|
|
30
|
-
model: str,
|
|
31
|
-
extra_body: dict[str, Any] | None = None,
|
|
32
|
-
prompt_compiler: InvestigationReadinessPromptCompilerProtocol | None = None,
|
|
33
|
-
) -> None:
|
|
34
|
-
self.client = client
|
|
35
|
-
self.model = model
|
|
36
|
-
self.extra_body = extra_body or {}
|
|
37
|
-
self.prompt_compiler = prompt_compiler or InvestigationReadinessPromptCompiler()
|
|
38
|
-
|
|
39
|
-
def assess(self, context: InvestigationContext) -> InvestigationReadinessDecision:
|
|
40
|
-
compiled_prompt = self.prompt_compiler.compile(context=context)
|
|
41
|
-
|
|
42
|
-
response = self.client.create_chat_completion(
|
|
43
|
-
ChatCompletionRequest(
|
|
44
|
-
model=self.model,
|
|
45
|
-
temperature=0.0,
|
|
46
|
-
extra_body=self.extra_body,
|
|
47
|
-
messages=[
|
|
48
|
-
ChatMessage(
|
|
49
|
-
role="system",
|
|
50
|
-
content=compiled_prompt.system_prompt,
|
|
51
|
-
),
|
|
52
|
-
ChatMessage(
|
|
53
|
-
role="user",
|
|
54
|
-
content=compiled_prompt.user_prompt,
|
|
55
|
-
),
|
|
56
|
-
],
|
|
57
|
-
)
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return parse_investigation_readiness_decision(response.content)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def parse_investigation_readiness_decision(value: str) -> InvestigationReadinessDecision:
|
|
64
|
-
cleaned = remove_thinking_blocks(value).strip()
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
payload = json.loads(cleaned)
|
|
68
|
-
except json.JSONDecodeError:
|
|
69
|
-
return InvestigationReadinessDecision(
|
|
70
|
-
ready_for_sql=False,
|
|
71
|
-
route=InvestigationRoute.ANALYSIS,
|
|
72
|
-
confidence=0.0,
|
|
73
|
-
reason="Investigation readiness agent returned invalid JSON.",
|
|
74
|
-
missing_information=["valid readiness JSON"],
|
|
75
|
-
required_analysis=["Retry readiness assessment with a valid JSON response."],
|
|
76
|
-
model_response={"raw": cleaned},
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
if not isinstance(payload, dict):
|
|
80
|
-
return InvestigationReadinessDecision(
|
|
81
|
-
ready_for_sql=False,
|
|
82
|
-
route=InvestigationRoute.ANALYSIS,
|
|
83
|
-
confidence=0.0,
|
|
84
|
-
reason="Investigation readiness agent returned a non-object JSON value.",
|
|
85
|
-
missing_information=["valid readiness JSON object"],
|
|
86
|
-
required_analysis=["Retry readiness assessment with a JSON object response."],
|
|
87
|
-
model_response={"raw": payload},
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
ready_for_sql = parse_bool(payload.get("ready_for_sql"))
|
|
91
|
-
route = parse_route(payload.get("route"), ready_for_sql)
|
|
92
|
-
|
|
93
|
-
if route == InvestigationRoute.SQL and not ready_for_sql:
|
|
94
|
-
route = InvestigationRoute.ANALYSIS
|
|
95
|
-
|
|
96
|
-
if route == InvestigationRoute.ANALYSIS:
|
|
97
|
-
ready_for_sql = False
|
|
98
|
-
|
|
99
|
-
missing_information = parse_string_list(payload.get("missing_information"))
|
|
100
|
-
required_analysis = parse_string_list(payload.get("required_analysis"))
|
|
101
|
-
|
|
102
|
-
return InvestigationReadinessDecision(
|
|
103
|
-
ready_for_sql=ready_for_sql,
|
|
104
|
-
route=route,
|
|
105
|
-
confidence=parse_confidence(payload.get("confidence")),
|
|
106
|
-
reason=str(payload.get("reason") or ""),
|
|
107
|
-
missing_information=missing_information,
|
|
108
|
-
required_analysis=required_analysis,
|
|
109
|
-
required_analysis_tasks=parse_required_analysis_tasks(
|
|
110
|
-
payload.get("required_analysis_tasks"),
|
|
111
|
-
missing_information,
|
|
112
|
-
required_analysis,
|
|
113
|
-
),
|
|
114
|
-
assumptions=parse_string_list(payload.get("assumptions")),
|
|
115
|
-
model_response=payload,
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def parse_route(value: object, ready_for_sql: bool) -> InvestigationRoute:
|
|
120
|
-
if isinstance(value, str):
|
|
121
|
-
normalized = value.strip().lower().replace("-", "_").replace(" ", "_")
|
|
122
|
-
if normalized in {"sql", "ready", "ready_for_sql"}:
|
|
123
|
-
return InvestigationRoute.SQL
|
|
124
|
-
if normalized in {"analysis", "analyze", "requires_analysis"}:
|
|
125
|
-
return InvestigationRoute.ANALYSIS
|
|
126
|
-
|
|
127
|
-
return InvestigationRoute.SQL if ready_for_sql else InvestigationRoute.ANALYSIS
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def parse_bool(value: object) -> bool:
|
|
131
|
-
if isinstance(value, bool):
|
|
132
|
-
return value
|
|
133
|
-
|
|
134
|
-
if isinstance(value, str):
|
|
135
|
-
return value.strip().lower() in {"true", "yes", "tak", "1"}
|
|
136
|
-
|
|
137
|
-
return False
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def parse_confidence(value: object) -> float:
|
|
141
|
-
try:
|
|
142
|
-
confidence = float(value)
|
|
143
|
-
except (TypeError, ValueError):
|
|
144
|
-
return 0.0
|
|
145
|
-
|
|
146
|
-
return max(0.0, min(1.0, confidence))
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def parse_string_list(value: object) -> list[str]:
|
|
150
|
-
if not isinstance(value, list):
|
|
151
|
-
return []
|
|
152
|
-
|
|
153
|
-
items: list[str] = []
|
|
154
|
-
for item in value:
|
|
155
|
-
if item is None:
|
|
156
|
-
continue
|
|
157
|
-
text = str(item).strip()
|
|
158
|
-
if text:
|
|
159
|
-
items.append(text)
|
|
160
|
-
|
|
161
|
-
return items
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def parse_required_analysis_tasks(
|
|
165
|
-
value: object,
|
|
166
|
-
missing_information: list[str],
|
|
167
|
-
required_analysis: list[str],
|
|
168
|
-
) -> list[RequiredAnalysisTask]:
|
|
169
|
-
if isinstance(value, list):
|
|
170
|
-
tasks = [
|
|
171
|
-
parse_required_analysis_task(item)
|
|
172
|
-
for item in value
|
|
173
|
-
if isinstance(item, dict)
|
|
174
|
-
]
|
|
175
|
-
tasks = [task for task in tasks if task.required_analysis]
|
|
176
|
-
if tasks:
|
|
177
|
-
return tasks
|
|
178
|
-
|
|
179
|
-
return required_analysis_tasks_from_lists(missing_information, required_analysis)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def parse_required_analysis_task(value: dict[str, object]) -> RequiredAnalysisTask:
|
|
183
|
-
return RequiredAnalysisTask(
|
|
184
|
-
missing_information=str(value.get("missing_information") or "").strip(),
|
|
185
|
-
required_analysis=str(value.get("required_analysis") or "").strip(),
|
|
186
|
-
category=normalize_analysis_category(value.get("category")),
|
|
187
|
-
expected_output=str(value.get("expected_output") or "").strip(),
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def required_analysis_tasks_from_lists(
|
|
192
|
-
missing_information: list[str],
|
|
193
|
-
required_analysis: list[str],
|
|
194
|
-
) -> list[RequiredAnalysisTask]:
|
|
195
|
-
tasks: list[RequiredAnalysisTask] = []
|
|
196
|
-
for index, analysis_question in enumerate(required_analysis):
|
|
197
|
-
tasks.append(
|
|
198
|
-
RequiredAnalysisTask(
|
|
199
|
-
missing_information=missing_information[index]
|
|
200
|
-
if index < len(missing_information)
|
|
201
|
-
else "",
|
|
202
|
-
required_analysis=analysis_question,
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
return tasks
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def normalize_analysis_category(value: object) -> str:
|
|
210
|
-
normalized = str(value or "unknown").strip().lower().replace("-", "_").replace(" ", "_")
|
|
211
|
-
allowed_categories = {
|
|
212
|
-
"dictionary_value",
|
|
213
|
-
"relationship_logic",
|
|
214
|
-
"filter_logic",
|
|
215
|
-
"aggregation_logic",
|
|
216
|
-
"entity_mapping",
|
|
217
|
-
"unknown",
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return normalized if normalized in allowed_categories else "unknown"
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
|
|
3
|
-
from gaard_core.investigation.models import (
|
|
4
|
-
InvestigationContext,
|
|
5
|
-
InvestigationIteration,
|
|
6
|
-
InvestigationLoopConfig,
|
|
7
|
-
InvestigationLoopResult,
|
|
8
|
-
InvestigationReadinessDecision,
|
|
9
|
-
InvestigationRoute,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class InvestigationReadinessAgent(Protocol):
|
|
14
|
-
name: str
|
|
15
|
-
|
|
16
|
-
def assess(self, context: InvestigationContext) -> InvestigationReadinessDecision:
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class InvestigationLoop:
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
readiness_agent: InvestigationReadinessAgent,
|
|
24
|
-
config: InvestigationLoopConfig | None = None,
|
|
25
|
-
) -> None:
|
|
26
|
-
self.readiness_agent = readiness_agent
|
|
27
|
-
self.config = config or InvestigationLoopConfig()
|
|
28
|
-
|
|
29
|
-
def run(self, context: InvestigationContext) -> InvestigationLoopResult:
|
|
30
|
-
iterations: list[InvestigationIteration] = []
|
|
31
|
-
|
|
32
|
-
for iteration_number in range(1, self.config.max_iterations + 1):
|
|
33
|
-
decision = self.readiness_agent.assess(context)
|
|
34
|
-
normalized_decision = self._normalize_decision(decision)
|
|
35
|
-
iterations.append(
|
|
36
|
-
InvestigationIteration(
|
|
37
|
-
iteration=iteration_number,
|
|
38
|
-
agent=self.readiness_agent.name,
|
|
39
|
-
decision=normalized_decision,
|
|
40
|
-
)
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
if normalized_decision.route == InvestigationRoute.SQL:
|
|
44
|
-
return InvestigationLoopResult(
|
|
45
|
-
route=InvestigationRoute.SQL,
|
|
46
|
-
ready_for_sql=True,
|
|
47
|
-
max_iterations=self.config.max_iterations,
|
|
48
|
-
confidence_threshold=self.config.readiness_confidence_threshold,
|
|
49
|
-
iterations=iterations,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
return InvestigationLoopResult(
|
|
53
|
-
route=InvestigationRoute.ANALYSIS,
|
|
54
|
-
ready_for_sql=False,
|
|
55
|
-
max_iterations=self.config.max_iterations,
|
|
56
|
-
confidence_threshold=self.config.readiness_confidence_threshold,
|
|
57
|
-
iterations=iterations,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return InvestigationLoopResult(
|
|
61
|
-
route=InvestigationRoute.ANALYSIS,
|
|
62
|
-
ready_for_sql=False,
|
|
63
|
-
max_iterations=self.config.max_iterations,
|
|
64
|
-
confidence_threshold=self.config.readiness_confidence_threshold,
|
|
65
|
-
iterations=iterations,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
def _normalize_decision(
|
|
69
|
-
self,
|
|
70
|
-
decision: InvestigationReadinessDecision,
|
|
71
|
-
) -> InvestigationReadinessDecision:
|
|
72
|
-
ready = (
|
|
73
|
-
decision.ready_for_sql
|
|
74
|
-
and decision.confidence >= self.config.readiness_confidence_threshold
|
|
75
|
-
)
|
|
76
|
-
route = InvestigationRoute.SQL if ready else InvestigationRoute.ANALYSIS
|
|
77
|
-
|
|
78
|
-
return decision.model_copy(
|
|
79
|
-
update={
|
|
80
|
-
"ready_for_sql": ready,
|
|
81
|
-
"route": route,
|
|
82
|
-
}
|
|
83
|
-
)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from gaard_core.investigation.models import (
|
|
2
|
-
InvestigationContext,
|
|
3
|
-
InvestigationReadinessDecision,
|
|
4
|
-
InvestigationRoute,
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MockInvestigationReadinessAgent:
|
|
9
|
-
name = "mock_investigation_readiness"
|
|
10
|
-
|
|
11
|
-
def __init__(self, decision: InvestigationReadinessDecision | None = None) -> None:
|
|
12
|
-
self.decision = decision or InvestigationReadinessDecision(
|
|
13
|
-
ready_for_sql=True,
|
|
14
|
-
route=InvestigationRoute.SQL,
|
|
15
|
-
confidence=1.0,
|
|
16
|
-
reason="Mock readiness agent allows the normal SQL pipeline.",
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
def assess(self, context: InvestigationContext) -> InvestigationReadinessDecision:
|
|
20
|
-
return self.decision
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from enum import StrEnum
|
|
2
|
-
from typing import Any
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class InvestigationRoute(StrEnum):
|
|
8
|
-
SQL = "sql"
|
|
9
|
-
ANALYSIS = "analysis"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class InvestigationContext(BaseModel):
|
|
13
|
-
question: str = Field(min_length=1)
|
|
14
|
-
datasource_id: str = "default"
|
|
15
|
-
user_id: str = "local-admin"
|
|
16
|
-
formatted_schema: str = ""
|
|
17
|
-
business_logic: str = ""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class RequiredAnalysisTask(BaseModel):
|
|
21
|
-
missing_information: str = ""
|
|
22
|
-
required_analysis: str = ""
|
|
23
|
-
category: str = "unknown"
|
|
24
|
-
expected_output: str = ""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class InvestigationReadinessDecision(BaseModel):
|
|
28
|
-
ready_for_sql: bool = False
|
|
29
|
-
route: InvestigationRoute = InvestigationRoute.ANALYSIS
|
|
30
|
-
confidence: float = 0.0
|
|
31
|
-
reason: str = ""
|
|
32
|
-
missing_information: list[str] = Field(default_factory=list)
|
|
33
|
-
required_analysis: list[str] = Field(default_factory=list)
|
|
34
|
-
required_analysis_tasks: list[RequiredAnalysisTask] = Field(default_factory=list)
|
|
35
|
-
assumptions: list[str] = Field(default_factory=list)
|
|
36
|
-
model_response: dict[str, Any] = Field(default_factory=dict)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class InvestigationIteration(BaseModel):
|
|
40
|
-
iteration: int
|
|
41
|
-
agent: str
|
|
42
|
-
decision: InvestigationReadinessDecision
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class InvestigationLoopConfig(BaseModel):
|
|
46
|
-
max_iterations: int = Field(default=1, ge=1)
|
|
47
|
-
readiness_confidence_threshold: float = Field(default=0.85, ge=0.0, le=1.0)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class InvestigationLoopResult(BaseModel):
|
|
51
|
-
route: InvestigationRoute
|
|
52
|
-
ready_for_sql: bool
|
|
53
|
-
max_iterations: int
|
|
54
|
-
confidence_threshold: float
|
|
55
|
-
iterations: list[InvestigationIteration] = Field(default_factory=list)
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def final_decision(self) -> InvestigationReadinessDecision | None:
|
|
59
|
-
if not self.iterations:
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
return self.iterations[-1].decision
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
from gaard_core.investigation.models import InvestigationContext, InvestigationRoute
|
|
2
|
-
from gaard_core.json_utils import json_dumps
|
|
3
|
-
from gaard_core.prompt_compiler.models import CompiledPrompt
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class InvestigationReadinessPromptCompiler:
|
|
7
|
-
def compile(self, context: InvestigationContext) -> CompiledPrompt:
|
|
8
|
-
payload = {
|
|
9
|
-
"question": context.question,
|
|
10
|
-
"datasource_id": context.datasource_id,
|
|
11
|
-
"user_id": context.user_id,
|
|
12
|
-
"schema": context.formatted_schema,
|
|
13
|
-
"business_logic": context.business_logic,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return CompiledPrompt(
|
|
17
|
-
system_prompt=self._build_system_prompt(),
|
|
18
|
-
user_prompt=self._build_user_prompt(payload),
|
|
19
|
-
metadata={
|
|
20
|
-
"allowed_routes": [item.value for item in InvestigationRoute],
|
|
21
|
-
},
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
def _build_system_prompt(self) -> str:
|
|
25
|
-
return """You are GAARD Investigation Readiness.
|
|
26
|
-
|
|
27
|
-
Your task is to decide whether GAARD already knows enough to create a correct SQL query for the user's question.
|
|
28
|
-
|
|
29
|
-
Assume nothing. Verify continuously.
|
|
30
|
-
|
|
31
|
-
Use only:
|
|
32
|
-
- the user's question,
|
|
33
|
-
- the active datasource schema,
|
|
34
|
-
- the approved or previously saved business logic supplied in the payload.
|
|
35
|
-
|
|
36
|
-
You do not generate SQL.
|
|
37
|
-
You do not answer the user.
|
|
38
|
-
You decide only whether normal SQL generation may start safely.
|
|
39
|
-
|
|
40
|
-
Return ready_for_sql=true only when all information needed for correct SQL is explicit in the question, schema, and business logic:
|
|
41
|
-
- requested business entity or metric,
|
|
42
|
-
- relevant tables, views and columns,
|
|
43
|
-
- required filters and dictionary/status values,
|
|
44
|
-
- required joins or relationships,
|
|
45
|
-
- requested output shape such as count, list, detail, or aggregation.
|
|
46
|
-
|
|
47
|
-
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.
|
|
48
|
-
|
|
49
|
-
Output rules:
|
|
50
|
-
- Return only a JSON object.
|
|
51
|
-
- Do not include markdown.
|
|
52
|
-
- Do not include reasoning outside the JSON.
|
|
53
|
-
- Do not include <think> blocks.
|
|
54
|
-
- Use exactly this JSON shape:
|
|
55
|
-
{"ready_for_sql":false,"route":"analysis","confidence":0.0,"reason":"short reason","missing_information":[],"required_analysis":[],"required_analysis_tasks":[],"assumptions":[]}
|
|
56
|
-
|
|
57
|
-
Required analysis task shape:
|
|
58
|
-
{"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"}
|
|
59
|
-
|
|
60
|
-
Allowed categories:
|
|
61
|
-
- dictionary_value
|
|
62
|
-
- relationship_logic
|
|
63
|
-
- filter_logic
|
|
64
|
-
- aggregation_logic
|
|
65
|
-
- entity_mapping
|
|
66
|
-
- unknown
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
def _build_user_prompt(self, payload: dict[str, str]) -> str:
|
|
70
|
-
return f"""Assess whether normal SQL generation can start.
|
|
71
|
-
|
|
72
|
-
Input JSON:
|
|
73
|
-
{json_dumps(payload, ensure_ascii=False, indent=2)}
|
|
74
|
-
|
|
75
|
-
Return one JSON object with:
|
|
76
|
-
- ready_for_sql: boolean
|
|
77
|
-
- route: sql or analysis
|
|
78
|
-
- confidence: number from 0 to 1
|
|
79
|
-
- reason: short explanation
|
|
80
|
-
- missing_information: list of missing or ambiguous items
|
|
81
|
-
- required_analysis: list of checks that Analysis mode should perform when ready_for_sql=false
|
|
82
|
-
- required_analysis_tasks: list of structured SQL-analysis tasks with missing_information, required_analysis, category, expected_output
|
|
83
|
-
- assumptions: list of any assumptions that would affect SQL correctness
|
|
84
|
-
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
from gaard_core.investigation.llm_readiness_agent import (
|
|
2
|
-
parse_investigation_readiness_decision,
|
|
3
|
-
)
|
|
4
|
-
from gaard_core.investigation.loop import InvestigationLoop
|
|
5
|
-
from gaard_core.investigation.mock_readiness_agent import MockInvestigationReadinessAgent
|
|
6
|
-
from gaard_core.investigation.models import (
|
|
7
|
-
InvestigationContext,
|
|
8
|
-
InvestigationLoopConfig,
|
|
9
|
-
InvestigationReadinessDecision,
|
|
10
|
-
InvestigationRoute,
|
|
11
|
-
RequiredAnalysisTask,
|
|
12
|
-
)
|
|
13
|
-
from gaard_core.prompt_compiler.investigation_readiness_prompt import (
|
|
14
|
-
InvestigationReadinessPromptCompiler,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_parse_readiness_decision_routes_ready_json_to_sql() -> None:
|
|
19
|
-
decision = parse_investigation_readiness_decision(
|
|
20
|
-
"""
|
|
21
|
-
{
|
|
22
|
-
"ready_for_sql": true,
|
|
23
|
-
"route": "sql",
|
|
24
|
-
"confidence": 0.94,
|
|
25
|
-
"reason": "Schema and business logic identify the needed columns.",
|
|
26
|
-
"missing_information": [],
|
|
27
|
-
"required_analysis": [],
|
|
28
|
-
"assumptions": []
|
|
29
|
-
}
|
|
30
|
-
"""
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
assert decision.ready_for_sql is True
|
|
34
|
-
assert decision.route == InvestigationRoute.SQL
|
|
35
|
-
assert decision.confidence == 0.94
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def test_parse_readiness_decision_invalid_json_requires_analysis() -> None:
|
|
39
|
-
decision = parse_investigation_readiness_decision("ready")
|
|
40
|
-
|
|
41
|
-
assert decision.ready_for_sql is False
|
|
42
|
-
assert decision.route == InvestigationRoute.ANALYSIS
|
|
43
|
-
assert "valid readiness JSON" in decision.missing_information
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def test_parse_readiness_decision_requires_consistent_ready_signal() -> None:
|
|
47
|
-
decision = parse_investigation_readiness_decision(
|
|
48
|
-
'{"ready_for_sql": false, "route": "sql", "confidence": 0.99}'
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
assert decision.ready_for_sql is False
|
|
52
|
-
assert decision.route == InvestigationRoute.ANALYSIS
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_parse_readiness_decision_reads_structured_required_analysis_tasks() -> None:
|
|
56
|
-
decision = parse_investigation_readiness_decision(
|
|
57
|
-
"""
|
|
58
|
-
{
|
|
59
|
-
"ready_for_sql": false,
|
|
60
|
-
"route": "analysis",
|
|
61
|
-
"confidence": 0.91,
|
|
62
|
-
"missing_information": ["specialty dictionary value"],
|
|
63
|
-
"required_analysis": ["List distinct doctors.specialization values."],
|
|
64
|
-
"required_analysis_tasks": [
|
|
65
|
-
{
|
|
66
|
-
"missing_information": "specialty dictionary value",
|
|
67
|
-
"required_analysis": "List distinct doctors.specialization values.",
|
|
68
|
-
"category": "dictionary-value",
|
|
69
|
-
"expected_output": "specialization values"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
"assumptions": []
|
|
73
|
-
}
|
|
74
|
-
"""
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
assert decision.required_analysis_tasks == [
|
|
78
|
-
RequiredAnalysisTask(
|
|
79
|
-
missing_information="specialty dictionary value",
|
|
80
|
-
required_analysis="List distinct doctors.specialization values.",
|
|
81
|
-
category="dictionary_value",
|
|
82
|
-
expected_output="specialization values",
|
|
83
|
-
)
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def test_parse_readiness_decision_builds_tasks_from_legacy_lists() -> None:
|
|
88
|
-
decision = parse_investigation_readiness_decision(
|
|
89
|
-
"""
|
|
90
|
-
{
|
|
91
|
-
"ready_for_sql": false,
|
|
92
|
-
"route": "analysis",
|
|
93
|
-
"confidence": 0.91,
|
|
94
|
-
"missing_information": ["specialty dictionary value"],
|
|
95
|
-
"required_analysis": ["List distinct doctors.specialization values."],
|
|
96
|
-
"assumptions": []
|
|
97
|
-
}
|
|
98
|
-
"""
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
assert decision.required_analysis_tasks == [
|
|
102
|
-
RequiredAnalysisTask(
|
|
103
|
-
missing_information="specialty dictionary value",
|
|
104
|
-
required_analysis="List distinct doctors.specialization values.",
|
|
105
|
-
)
|
|
106
|
-
]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def test_investigation_loop_requires_confidence_threshold_for_sql() -> None:
|
|
110
|
-
agent = MockInvestigationReadinessAgent(
|
|
111
|
-
InvestigationReadinessDecision(
|
|
112
|
-
ready_for_sql=True,
|
|
113
|
-
route=InvestigationRoute.SQL,
|
|
114
|
-
confidence=0.5,
|
|
115
|
-
reason="Low confidence.",
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
result = InvestigationLoop(
|
|
120
|
-
readiness_agent=agent,
|
|
121
|
-
config=InvestigationLoopConfig(
|
|
122
|
-
max_iterations=1,
|
|
123
|
-
readiness_confidence_threshold=0.85,
|
|
124
|
-
),
|
|
125
|
-
).run(
|
|
126
|
-
InvestigationContext(
|
|
127
|
-
question="ile jest wizyt",
|
|
128
|
-
formatted_schema="Table: appointments",
|
|
129
|
-
)
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
assert result.ready_for_sql is False
|
|
133
|
-
assert result.route == InvestigationRoute.ANALYSIS
|
|
134
|
-
assert result.iterations[0].decision.ready_for_sql is False
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def test_readiness_prompt_includes_schema_and_business_logic() -> None:
|
|
138
|
-
compiled = InvestigationReadinessPromptCompiler().compile(
|
|
139
|
-
InvestigationContext(
|
|
140
|
-
question="ile jest aktywnych pacjentów",
|
|
141
|
-
datasource_id="medical",
|
|
142
|
-
user_id="alice",
|
|
143
|
-
formatted_schema="Table: patients",
|
|
144
|
-
business_logic="Active patient means patients.status = 'active'.",
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
assert "Assume nothing. Verify continuously." in compiled.system_prompt
|
|
149
|
-
assert "Table: patients" in compiled.user_prompt
|
|
150
|
-
assert "patients.status = 'active'" in compiled.user_prompt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gaard_core-0.1.0/src/gaard_core/audit → gaard_core-0.2.0/src/gaard_core/execution}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/intent_classification_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
{gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/result_classification_prompt.py
RENAMED
|
File without changes
|
{gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/result_interpretation_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
{gaard_core-0.1.0 → gaard_core-0.2.0}/src/gaard_core/prompt_compiler/sql_generation_prompt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|