langchain-timbr 1.5.0__py3-none-any.whl
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.
- langchain_timbr/__init__.py +17 -0
- langchain_timbr/config.py +21 -0
- langchain_timbr/langchain/__init__.py +16 -0
- langchain_timbr/langchain/execute_timbr_query_chain.py +307 -0
- langchain_timbr/langchain/generate_answer_chain.py +99 -0
- langchain_timbr/langchain/generate_timbr_sql_chain.py +176 -0
- langchain_timbr/langchain/identify_concept_chain.py +138 -0
- langchain_timbr/langchain/timbr_sql_agent.py +418 -0
- langchain_timbr/langchain/validate_timbr_sql_chain.py +187 -0
- langchain_timbr/langgraph/__init__.py +13 -0
- langchain_timbr/langgraph/execute_timbr_query_node.py +108 -0
- langchain_timbr/langgraph/generate_response_node.py +59 -0
- langchain_timbr/langgraph/generate_timbr_sql_node.py +98 -0
- langchain_timbr/langgraph/identify_concept_node.py +78 -0
- langchain_timbr/langgraph/validate_timbr_query_node.py +100 -0
- langchain_timbr/llm_wrapper/llm_wrapper.py +189 -0
- langchain_timbr/llm_wrapper/timbr_llm_wrapper.py +41 -0
- langchain_timbr/timbr_llm_connector.py +398 -0
- langchain_timbr/utils/general.py +70 -0
- langchain_timbr/utils/prompt_service.py +330 -0
- langchain_timbr/utils/temperature_supported_models.json +62 -0
- langchain_timbr/utils/timbr_llm_utils.py +575 -0
- langchain_timbr/utils/timbr_utils.py +475 -0
- langchain_timbr-1.5.0.dist-info/METADATA +103 -0
- langchain_timbr-1.5.0.dist-info/RECORD +27 -0
- langchain_timbr-1.5.0.dist-info/WHEEL +4 -0
- langchain_timbr-1.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Optional, Union, Dict, Any
|
|
2
|
+
from langchain.chains.base import Chain
|
|
3
|
+
from langchain.llms.base import LLM
|
|
4
|
+
|
|
5
|
+
from ..utils.general import parse_list, to_boolean, to_integer
|
|
6
|
+
from ..utils.timbr_llm_utils import determine_concept
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IdentifyTimbrConceptChain(Chain):
|
|
10
|
+
"""
|
|
11
|
+
LangChain chain for identifying relevant concepts from user prompts using Timbr knowledge graphs.
|
|
12
|
+
|
|
13
|
+
This chain analyzes natural language prompts to determine the most appropriate concept(s)
|
|
14
|
+
within a Timbr ontology/knowledge graph that best matches the user's intent. It uses an LLM
|
|
15
|
+
to process prompts and connects to Timbr via URL and token for concept identification.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
llm: LLM,
|
|
21
|
+
url: str,
|
|
22
|
+
token: str,
|
|
23
|
+
ontology: str,
|
|
24
|
+
concepts_list: Optional[Union[list[str], str]] = None,
|
|
25
|
+
views_list: Optional[Union[list[str], str]] = None,
|
|
26
|
+
include_logic_concepts: Optional[bool] = False,
|
|
27
|
+
include_tags: Optional[Union[list[str], str]] = None,
|
|
28
|
+
should_validate: Optional[bool] = False,
|
|
29
|
+
retries: Optional[int] = 3,
|
|
30
|
+
note: Optional[str] = '',
|
|
31
|
+
verify_ssl: Optional[bool] = True,
|
|
32
|
+
is_jwt: Optional[bool] = False,
|
|
33
|
+
jwt_tenant_id: Optional[str] = None,
|
|
34
|
+
conn_params: Optional[dict] = None,
|
|
35
|
+
debug: Optional[bool] = False,
|
|
36
|
+
**kwargs,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
:param llm: An LLM instance or a function that takes a prompt string and returns the LLM’s response
|
|
40
|
+
:param url: Timbr server url
|
|
41
|
+
:param token: Timbr password or token value
|
|
42
|
+
:param ontology: The name of the ontology/knowledge graph
|
|
43
|
+
:param concepts_list: Optional specific concept options to query
|
|
44
|
+
:param views_list: Optional specific view options to query
|
|
45
|
+
:param include_logic_concepts: Optional boolean to include logic concepts (concepts without unique properties which only inherits from an upper level concept with filter logic) in the query.
|
|
46
|
+
:param include_tags: Optional specific concepts & properties tag options to use in the query (Disabled by default. Use '*' to enable all tags or a string represents a list of tags divided by commas (e.g. 'tag1,tag2')
|
|
47
|
+
:param should_validate: Whether to validate the identified concept before returning it
|
|
48
|
+
:param retries: Number of retry attempts if the identified concept is invalid
|
|
49
|
+
:param note: Optional additional note to extend our llm prompt
|
|
50
|
+
:param verify_ssl: Whether to verify SSL certificates (default is True).
|
|
51
|
+
:param is_jwt: Whether to use JWT authentication (default is False).
|
|
52
|
+
:param jwt_tenant_id: JWT tenant ID for multi-tenant environments (required when is_jwt=True).
|
|
53
|
+
:param conn_params: Extra Timbr connection parameters sent with every request (e.g., 'x-api-impersonate-user').
|
|
54
|
+
:param kwargs: Additional arguments to pass to the base
|
|
55
|
+
|
|
56
|
+
## Example
|
|
57
|
+
```
|
|
58
|
+
identify_timbr_concept_chain = IdentifyTimbrConceptChain(
|
|
59
|
+
url=<url>,
|
|
60
|
+
token=<token>,
|
|
61
|
+
llm=<llm or timbr_llm_wrapper instance>,
|
|
62
|
+
ontology=<ontology_name>,
|
|
63
|
+
concepts_list=<concepts>,
|
|
64
|
+
views_list=<views>,
|
|
65
|
+
include_tags=<tags>,
|
|
66
|
+
note=<note>,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return identify_timbr_concept_chain.invoke({ "prompt": question }).get("concept", None)
|
|
70
|
+
```
|
|
71
|
+
"""
|
|
72
|
+
super().__init__(**kwargs)
|
|
73
|
+
self._llm = llm
|
|
74
|
+
self._url = url
|
|
75
|
+
self._token = token
|
|
76
|
+
self._ontology = ontology
|
|
77
|
+
self._concepts_list = parse_list(concepts_list)
|
|
78
|
+
self._views_list = parse_list(views_list)
|
|
79
|
+
self._include_logic_concepts = to_boolean(include_logic_concepts)
|
|
80
|
+
self._include_tags = parse_list(include_tags)
|
|
81
|
+
self._should_validate = to_boolean(should_validate)
|
|
82
|
+
self._retries = to_integer(retries)
|
|
83
|
+
self._note = note
|
|
84
|
+
self._verify_ssl = to_boolean(verify_ssl)
|
|
85
|
+
self._is_jwt = to_boolean(is_jwt)
|
|
86
|
+
self._jwt_tenant_id = jwt_tenant_id
|
|
87
|
+
self._debug = to_boolean(debug)
|
|
88
|
+
self._conn_params = conn_params or {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def usage_metadata_key(self) -> str:
|
|
93
|
+
return "identify_concept_usage_metadata"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def input_keys(self) -> list:
|
|
98
|
+
return ["prompt"]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def output_keys(self) -> list:
|
|
103
|
+
return ["schema", "concept", "concept_metadata", self.usage_metadata_key]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_conn_params(self) -> dict:
|
|
107
|
+
return {
|
|
108
|
+
"url": self._url,
|
|
109
|
+
"token": self._token,
|
|
110
|
+
"ontology": self._ontology,
|
|
111
|
+
"verify_ssl": self._verify_ssl,
|
|
112
|
+
"is_jwt": self._is_jwt,
|
|
113
|
+
"jwt_tenant_id": self._jwt_tenant_id,
|
|
114
|
+
**self._conn_params,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _call(self, inputs: Dict[str, Any], run_manager=None) -> Dict[str, str]:
|
|
119
|
+
prompt = inputs["prompt"]
|
|
120
|
+
res = determine_concept(
|
|
121
|
+
question=prompt,
|
|
122
|
+
llm=self._llm,
|
|
123
|
+
conn_params=self._get_conn_params(),
|
|
124
|
+
concepts_list=self._concepts_list,
|
|
125
|
+
views_list=self._views_list,
|
|
126
|
+
include_logic_concepts=self._include_logic_concepts,
|
|
127
|
+
include_tags=self._include_tags,
|
|
128
|
+
should_validate=self._should_validate,
|
|
129
|
+
retries=self._retries,
|
|
130
|
+
note=self._note,
|
|
131
|
+
debug=self._debug,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
usage_metadata = res.pop("usage_metadata", {})
|
|
135
|
+
return {
|
|
136
|
+
**res,
|
|
137
|
+
self.usage_metadata_key: usage_metadata,
|
|
138
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
from typing import Optional, Any, Union
|
|
2
|
+
from langchain.agents import AgentExecutor, BaseSingleActionAgent
|
|
3
|
+
from langchain.llms.base import LLM
|
|
4
|
+
from langchain.schema import AgentAction, AgentFinish
|
|
5
|
+
|
|
6
|
+
from ..utils.general import parse_list, to_boolean, to_integer
|
|
7
|
+
from .execute_timbr_query_chain import ExecuteTimbrQueryChain
|
|
8
|
+
from .generate_answer_chain import GenerateAnswerChain
|
|
9
|
+
|
|
10
|
+
class TimbrSqlAgent(BaseSingleActionAgent):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
llm: LLM,
|
|
14
|
+
url: str,
|
|
15
|
+
token: str,
|
|
16
|
+
ontology: str,
|
|
17
|
+
schema: Optional[str] = 'dtimbr',
|
|
18
|
+
concept: Optional[str] = None,
|
|
19
|
+
concepts_list: Optional[Union[list[str], str]] = None,
|
|
20
|
+
views_list: Optional[Union[list[str], str]] = None,
|
|
21
|
+
include_logic_concepts: Optional[bool] = False,
|
|
22
|
+
include_tags: Optional[Union[list[str], str]] = None,
|
|
23
|
+
exclude_properties: Optional[Union[list[str], str]] = ['entity_id', 'entity_type', 'entity_label'],
|
|
24
|
+
should_validate_sql: Optional[bool] = True,
|
|
25
|
+
retries: Optional[int] = 3,
|
|
26
|
+
max_limit: Optional[int] = 500,
|
|
27
|
+
retry_if_no_results: Optional[bool] = False,
|
|
28
|
+
no_results_max_retries: Optional[int] = 2,
|
|
29
|
+
generate_answer: Optional[bool] = False,
|
|
30
|
+
note: Optional[str] = '',
|
|
31
|
+
db_is_case_sensitive: Optional[bool] = False,
|
|
32
|
+
graph_depth: Optional[int] = 1,
|
|
33
|
+
verify_ssl: Optional[bool] = True,
|
|
34
|
+
is_jwt: Optional[bool] = False,
|
|
35
|
+
jwt_tenant_id: Optional[str] = None,
|
|
36
|
+
conn_params: Optional[dict] = None,
|
|
37
|
+
debug: Optional[bool] = False
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
:param llm: Language model to use
|
|
41
|
+
:param url: Timbr server URL
|
|
42
|
+
:param token: Timbr authentication token
|
|
43
|
+
:param ontology: Name of the ontology/knowledge graph
|
|
44
|
+
:param schema: Optional specific schema name to query
|
|
45
|
+
:param concept: Optional specific concept name to query
|
|
46
|
+
:param concepts_list: Optional specific concept options to query
|
|
47
|
+
:param views_list: Optional specific view options to query
|
|
48
|
+
:param include_logic_concepts: Optional boolean to include logic concepts (concepts without unique properties which only inherits from an upper level concept with filter logic) in the query.
|
|
49
|
+
:param include_tags: Optional specific concepts & properties tag options to use in the query (Disabled by default). Use '*' to enable all tags or a string represents a list of tags divided by commas (e.g. 'tag1,tag2')
|
|
50
|
+
:param exclude_properties: Optional specific properties to exclude from the query (entity_id, entity_type & entity_label by default).
|
|
51
|
+
:param should_validate_sql: Whether to validate the SQL before executing it
|
|
52
|
+
:param retries: Number of retry attempts if the generated SQL is invalid
|
|
53
|
+
:param max_limit: Maximum number of rows to return
|
|
54
|
+
:retry_if_no_results: Whether to infer the result value from the SQL query. If the query won't return any rows, it will try to re-generate the SQL query then re-run it.
|
|
55
|
+
:param no_results_max_retries: Number of retry attempts to infer the result value from the SQL query
|
|
56
|
+
:param generate_answer: Whether to generate a natural language answer from the query results (default is False, which means the agent will return the SQL and rows only).
|
|
57
|
+
:param note: Optional additional note to extend our llm prompt
|
|
58
|
+
:param db_is_case_sensitive: Whether the database is case sensitive (default is False).
|
|
59
|
+
:param graph_depth: Maximum number of relationship hops to traverse from the source concept during schema exploration (default is 1).
|
|
60
|
+
:param verify_ssl: Whether to verify SSL certificates (default is True).
|
|
61
|
+
:param is_jwt: Whether to use JWT authentication (default is False).
|
|
62
|
+
:param jwt_tenant_id: JWT tenant ID for multi-tenant environments (required when is_jwt=True).
|
|
63
|
+
:param conn_params: Extra Timbr connection parameters sent with every request (e.g., 'x-api-impersonate-user').
|
|
64
|
+
|
|
65
|
+
## Example
|
|
66
|
+
```
|
|
67
|
+
agent = TimbrSqlAgent(
|
|
68
|
+
llm=<llm>,
|
|
69
|
+
url=<url>,
|
|
70
|
+
token=<token>,
|
|
71
|
+
ontology=<ontology>,
|
|
72
|
+
schema=<schema>,
|
|
73
|
+
concept=<concept>,
|
|
74
|
+
concepts_list=<concepts>,
|
|
75
|
+
views_list=<views>,
|
|
76
|
+
should_validate_sql=<should_validate_sql>,
|
|
77
|
+
retries=<retries>,
|
|
78
|
+
note=<note>,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
"""
|
|
82
|
+
super().__init__()
|
|
83
|
+
self._chain = ExecuteTimbrQueryChain(
|
|
84
|
+
llm=llm,
|
|
85
|
+
url=url,
|
|
86
|
+
token=token,
|
|
87
|
+
ontology=ontology,
|
|
88
|
+
schema=schema,
|
|
89
|
+
concept=concept,
|
|
90
|
+
concepts_list=parse_list(concepts_list),
|
|
91
|
+
views_list=parse_list(views_list),
|
|
92
|
+
include_logic_concepts=to_boolean(include_logic_concepts),
|
|
93
|
+
include_tags=parse_list(include_tags),
|
|
94
|
+
exclude_properties=parse_list(exclude_properties),
|
|
95
|
+
should_validate_sql=to_boolean(should_validate_sql),
|
|
96
|
+
retries=to_integer(retries),
|
|
97
|
+
max_limit=to_integer(max_limit),
|
|
98
|
+
retry_if_no_results=to_boolean(retry_if_no_results),
|
|
99
|
+
no_results_max_retries=to_integer(no_results_max_retries),
|
|
100
|
+
note=note,
|
|
101
|
+
db_is_case_sensitive=to_boolean(db_is_case_sensitive),
|
|
102
|
+
graph_depth=to_integer(graph_depth),
|
|
103
|
+
verify_ssl=to_boolean(verify_ssl),
|
|
104
|
+
is_jwt=to_boolean(is_jwt),
|
|
105
|
+
jwt_tenant_id=jwt_tenant_id,
|
|
106
|
+
conn_params=conn_params,
|
|
107
|
+
debug=to_boolean(debug),
|
|
108
|
+
)
|
|
109
|
+
self._generate_answer = to_boolean(generate_answer)
|
|
110
|
+
|
|
111
|
+
# Pre-initialize the answer chain to avoid creating it on every request
|
|
112
|
+
self._answer_chain = GenerateAnswerChain(
|
|
113
|
+
llm=llm,
|
|
114
|
+
url=url,
|
|
115
|
+
token=token,
|
|
116
|
+
verify_ssl=to_boolean(verify_ssl),
|
|
117
|
+
is_jwt=to_boolean(is_jwt),
|
|
118
|
+
jwt_tenant_id=jwt_tenant_id,
|
|
119
|
+
conn_params=conn_params,
|
|
120
|
+
debug=to_boolean(debug),
|
|
121
|
+
) if self._generate_answer else None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _should_skip_answer_generation(self, result: dict) -> bool:
|
|
125
|
+
"""
|
|
126
|
+
Determine if answer generation should be skipped based on result content.
|
|
127
|
+
This can save LLM calls when there's an error or no meaningful data.
|
|
128
|
+
"""
|
|
129
|
+
if not self._generate_answer:
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
# Skip if there's an error
|
|
133
|
+
if result.get("error"):
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
# Skip if no rows returned
|
|
137
|
+
rows = result.get("rows", [])
|
|
138
|
+
if not rows or len(rows) == 0:
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def input_keys(self) -> list[str]:
|
|
146
|
+
"""Get the input keys required by the agent."""
|
|
147
|
+
return ["input"]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def plan(
|
|
151
|
+
self, intermediate_steps: list[tuple[AgentAction, str]], **kwargs: Any
|
|
152
|
+
) -> Union[AgentAction, AgentFinish]:
|
|
153
|
+
"""Plan the next action based on the input."""
|
|
154
|
+
user_input = kwargs.get("input", "")
|
|
155
|
+
|
|
156
|
+
# Enhanced input validation
|
|
157
|
+
if not user_input or not user_input.strip():
|
|
158
|
+
return AgentFinish(
|
|
159
|
+
return_values={
|
|
160
|
+
"error": "No input provided or input is empty",
|
|
161
|
+
"answer": None,
|
|
162
|
+
"rows": None,
|
|
163
|
+
"sql": None,
|
|
164
|
+
"schema": None,
|
|
165
|
+
"concept": None,
|
|
166
|
+
"usage_metadata": {},
|
|
167
|
+
},
|
|
168
|
+
log="Empty input received"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
result = self._chain.invoke({ "prompt": user_input })
|
|
173
|
+
answer = None
|
|
174
|
+
usage_metadata = result.get(self._chain.usage_metadata_key, {})
|
|
175
|
+
|
|
176
|
+
if self._answer_chain and not self._should_skip_answer_generation(result):
|
|
177
|
+
answer_res = self._answer_chain.invoke({
|
|
178
|
+
"prompt": user_input,
|
|
179
|
+
"rows": result.get("rows"),
|
|
180
|
+
"sql": result.get("sql")
|
|
181
|
+
})
|
|
182
|
+
answer = answer_res.get("answer", "")
|
|
183
|
+
usage_metadata.update(answer_res.get(self._answer_chain.usage_metadata_key, {}))
|
|
184
|
+
|
|
185
|
+
return AgentFinish(
|
|
186
|
+
return_values={
|
|
187
|
+
"answer": answer,
|
|
188
|
+
"rows": result.get("rows", []),
|
|
189
|
+
"sql": result.get("sql", ""),
|
|
190
|
+
"schema": result.get("schema", ""),
|
|
191
|
+
"concept": result.get("concept", ""),
|
|
192
|
+
"error": result.get("error", None),
|
|
193
|
+
"usage_metadata": usage_metadata,
|
|
194
|
+
},
|
|
195
|
+
log=f"Successfully executed query on concept: {result.get('concept', '')}"
|
|
196
|
+
)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
error_context = f"Error in TimbrSqlAgent.plan (sync): {str(e)}"
|
|
199
|
+
return AgentFinish(
|
|
200
|
+
return_values={
|
|
201
|
+
"error": str(e),
|
|
202
|
+
"answer": None,
|
|
203
|
+
"rows": None,
|
|
204
|
+
"sql": None,
|
|
205
|
+
"schema": None,
|
|
206
|
+
"concept": None,
|
|
207
|
+
"usage_metadata": {},
|
|
208
|
+
},
|
|
209
|
+
log=error_context
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
async def aplan(
|
|
213
|
+
self, intermediate_steps: list[tuple[AgentAction, str]], **kwargs: Any
|
|
214
|
+
) -> Union[AgentAction, AgentFinish]:
|
|
215
|
+
"""Async version of the plan method."""
|
|
216
|
+
user_input = kwargs.get("input", "")
|
|
217
|
+
|
|
218
|
+
if not user_input or not user_input.strip():
|
|
219
|
+
return AgentFinish(
|
|
220
|
+
return_values={
|
|
221
|
+
"error": "No input provided or input is empty",
|
|
222
|
+
"answer": None,
|
|
223
|
+
"rows": None,
|
|
224
|
+
"sql": None,
|
|
225
|
+
"schema": None,
|
|
226
|
+
"concept": None,
|
|
227
|
+
"usage_metadata": {},
|
|
228
|
+
},
|
|
229
|
+
log="Empty or whitespace-only input received"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
try:
|
|
233
|
+
# Use async invoke if available, fallback to sync
|
|
234
|
+
if hasattr(self._chain, 'ainvoke'):
|
|
235
|
+
result = await self._chain.ainvoke({ "prompt": user_input })
|
|
236
|
+
else:
|
|
237
|
+
result = self._chain.invoke({ "prompt": user_input })
|
|
238
|
+
|
|
239
|
+
answer = None
|
|
240
|
+
usage_metadata = result.get("usage_metadata", {})
|
|
241
|
+
|
|
242
|
+
if not self._should_skip_answer_generation(result) and self._answer_chain:
|
|
243
|
+
# Use async invoke if available for answer chain too
|
|
244
|
+
if hasattr(self._answer_chain, 'ainvoke'):
|
|
245
|
+
answer_res = await self._answer_chain.ainvoke({
|
|
246
|
+
"prompt": user_input,
|
|
247
|
+
"rows": result.get("rows"),
|
|
248
|
+
"sql": result.get("sql")
|
|
249
|
+
})
|
|
250
|
+
else:
|
|
251
|
+
answer_res = self._answer_chain.invoke({
|
|
252
|
+
"prompt": user_input,
|
|
253
|
+
"rows": result.get("rows"),
|
|
254
|
+
"sql": result.get("sql")
|
|
255
|
+
})
|
|
256
|
+
answer = answer_res.get("answer", "")
|
|
257
|
+
usage_metadata.update(answer_res.get(self._answer_chain.usage_metadata_key, {}))
|
|
258
|
+
|
|
259
|
+
return AgentFinish(
|
|
260
|
+
return_values={
|
|
261
|
+
"answer": answer,
|
|
262
|
+
"rows": result.get("rows", []),
|
|
263
|
+
"sql": result.get("sql", ""),
|
|
264
|
+
"schema": result.get("schema", ""),
|
|
265
|
+
"concept": result.get("concept", ""),
|
|
266
|
+
"error": result.get("error", None),
|
|
267
|
+
"usage_metadata": usage_metadata,
|
|
268
|
+
},
|
|
269
|
+
log=f"Successfully executed query on concept: {result.get('concept', '')}"
|
|
270
|
+
)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
error_context = f"Error in TimbrSqlAgent.aplan (async): {str(e)}"
|
|
273
|
+
return AgentFinish(
|
|
274
|
+
return_values={
|
|
275
|
+
"error": str(e),
|
|
276
|
+
"answer": None,
|
|
277
|
+
"rows": None,
|
|
278
|
+
"sql": None,
|
|
279
|
+
"schema": None,
|
|
280
|
+
"concept": None,
|
|
281
|
+
"usage_metadata": {},
|
|
282
|
+
},
|
|
283
|
+
log=error_context
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def return_values(self) -> list[str]:
|
|
288
|
+
"""Get the return values that this agent can produce."""
|
|
289
|
+
return [
|
|
290
|
+
"answer",
|
|
291
|
+
"rows",
|
|
292
|
+
"sql",
|
|
293
|
+
"schema",
|
|
294
|
+
"concept",
|
|
295
|
+
"error",
|
|
296
|
+
"usage_metadata",
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def create_timbr_sql_agent(
|
|
301
|
+
llm: LLM,
|
|
302
|
+
url: str,
|
|
303
|
+
token: str,
|
|
304
|
+
ontology: str,
|
|
305
|
+
schema: Optional[str] = 'dtimbr',
|
|
306
|
+
concept: Optional[str] = None,
|
|
307
|
+
concepts_list: Optional[Union[list[str], str]] = None,
|
|
308
|
+
views_list: Optional[Union[list[str], str]] = None,
|
|
309
|
+
include_logic_concepts: Optional[bool] = False,
|
|
310
|
+
include_tags: Optional[Union[list[str], str]] = None,
|
|
311
|
+
exclude_properties: Optional[Union[list[str], str]] = ['entity_id', 'entity_type', 'entity_label'],
|
|
312
|
+
should_validate_sql: Optional[bool] = True,
|
|
313
|
+
retries: Optional[int] = 3,
|
|
314
|
+
max_limit: Optional[int] = 500,
|
|
315
|
+
retry_if_no_results: Optional[bool] = False,
|
|
316
|
+
no_results_max_retries: Optional[int] = 2,
|
|
317
|
+
generate_answer: Optional[bool] = False,
|
|
318
|
+
note: Optional[str] = '',
|
|
319
|
+
db_is_case_sensitive: Optional[bool] = False,
|
|
320
|
+
graph_depth: Optional[int] = 1,
|
|
321
|
+
verify_ssl: Optional[bool] = True,
|
|
322
|
+
is_jwt: Optional[bool] = False,
|
|
323
|
+
jwt_tenant_id: Optional[str] = None,
|
|
324
|
+
conn_params: Optional[dict] = None,
|
|
325
|
+
debug: Optional[bool] = False
|
|
326
|
+
) -> AgentExecutor:
|
|
327
|
+
"""
|
|
328
|
+
Create and configure a Timbr agent with its executor.
|
|
329
|
+
|
|
330
|
+
:param llm: Language model to use
|
|
331
|
+
:param url: Timbr server URL
|
|
332
|
+
:param token: Timbr authentication token
|
|
333
|
+
:param ontology: Name of the ontology/knowledge graph
|
|
334
|
+
:param schema: Optional specific schema name to query
|
|
335
|
+
:param concept: Optional specific concept name to query
|
|
336
|
+
:param concepts_list: Optional specific concept options to query
|
|
337
|
+
:param views_list: Optional specific view options to query
|
|
338
|
+
:param include_logic_concepts: Optional boolean to include logic concepts (concepts without unique properties which only inherits from an upper level concept with filter logic) in the query.
|
|
339
|
+
:param include_tags: Optional specific concepts & properties tag options to use in the query (Disabled by default. Use '*' to enable all tags or a string represents a list of tags divided by commas (e.g. 'tag1,tag2')
|
|
340
|
+
:param exclude_properties: Optional specific properties to exclude from the query (entity_id, entity_type & entity_label by default).
|
|
341
|
+
:param should_validate_sql: Whether to validate the SQL before executing it
|
|
342
|
+
:param retries: Number of retry attempts if the generated SQL is invalid
|
|
343
|
+
:param max_limit: Maximum number of rows to return
|
|
344
|
+
:retry_if_no_results: Whether to infer the result value from the SQL query. If the query won't return any rows, it will try to re-generate the SQL query then re-run it.
|
|
345
|
+
:param no_results_max_retries: Number of retry attempts to infer the result value from the SQL query
|
|
346
|
+
:param generate_answer: Whether to generate an LLM answer based on the SQL results (default is False, which means the agent will return the SQL and rows only).
|
|
347
|
+
:param note: Optional additional note to extend our llm prompt
|
|
348
|
+
:param db_is_case_sensitive: Whether the database is case sensitive (default is False).
|
|
349
|
+
:param graph_depth: Maximum number of relationship hops to traverse from the source concept during schema exploration (default is 1).
|
|
350
|
+
:param verify_ssl: Whether to verify SSL certificates (default is True).
|
|
351
|
+
:param is_jwt: Whether to use JWT authentication (default is False).
|
|
352
|
+
:param jwt_tenant_id: JWT tenant ID for multi-tenant environments (required when is_jwt=True).
|
|
353
|
+
:param conn_params: Extra Timbr connection parameters sent with every request (e.g., 'x-api-impersonate-user').
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
AgentExecutor: Configured agent executor ready to use
|
|
357
|
+
|
|
358
|
+
## Example
|
|
359
|
+
```
|
|
360
|
+
agent = create_timbr_sql_agent(
|
|
361
|
+
llm=<llm>,
|
|
362
|
+
url=<url>,
|
|
363
|
+
token=<token>,
|
|
364
|
+
ontology=<ontology>,
|
|
365
|
+
schema=<schema>,
|
|
366
|
+
concept=<concept>,
|
|
367
|
+
concepts_list=<concepts>,
|
|
368
|
+
views_list=<views>,
|
|
369
|
+
include_tags=<tags>,
|
|
370
|
+
exclude_properties=<properties>,
|
|
371
|
+
should_validate_sql=<should_validate_sql>,
|
|
372
|
+
retries=<retries>,
|
|
373
|
+
note=<note>,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
result = agent.invoke("What are the total sales for last month?")
|
|
377
|
+
|
|
378
|
+
# Access the components of the result:
|
|
379
|
+
rows = result["rows"]
|
|
380
|
+
sql = result["sql"]
|
|
381
|
+
schema = result["schema"]
|
|
382
|
+
concept = result["concept"]
|
|
383
|
+
error = result["error"]
|
|
384
|
+
```
|
|
385
|
+
"""
|
|
386
|
+
agent = TimbrSqlAgent(
|
|
387
|
+
llm=llm,
|
|
388
|
+
url=url,
|
|
389
|
+
token=token,
|
|
390
|
+
ontology=ontology,
|
|
391
|
+
schema=schema,
|
|
392
|
+
concept=concept,
|
|
393
|
+
concepts_list=concepts_list,
|
|
394
|
+
views_list=views_list,
|
|
395
|
+
include_logic_concepts=include_logic_concepts,
|
|
396
|
+
include_tags=include_tags,
|
|
397
|
+
exclude_properties=exclude_properties,
|
|
398
|
+
should_validate_sql=should_validate_sql,
|
|
399
|
+
retries=retries,
|
|
400
|
+
max_limit=max_limit,
|
|
401
|
+
retry_if_no_results=retry_if_no_results,
|
|
402
|
+
no_results_max_retries=no_results_max_retries,
|
|
403
|
+
generate_answer=generate_answer,
|
|
404
|
+
note=note,
|
|
405
|
+
db_is_case_sensitive=db_is_case_sensitive,
|
|
406
|
+
graph_depth=graph_depth,
|
|
407
|
+
verify_ssl=verify_ssl,
|
|
408
|
+
is_jwt=is_jwt,
|
|
409
|
+
jwt_tenant_id=jwt_tenant_id,
|
|
410
|
+
conn_params=conn_params,
|
|
411
|
+
debug=debug,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
return AgentExecutor.from_agent_and_tools(
|
|
415
|
+
agent=agent,
|
|
416
|
+
tools=[], # No tools needed as we're directly using the chain
|
|
417
|
+
verbose=True
|
|
418
|
+
)
|