langchain-timbr 2.1.14__py3-none-any.whl → 3.0.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.
@@ -268,6 +268,7 @@ def determine_concept(
268
268
  ) -> dict[str, Any]:
269
269
  usage_metadata = {}
270
270
  determined_concept_name = None
271
+ identify_concept_reason = None
271
272
  schema = 'dtimbr'
272
273
 
273
274
  # Use config default timeout if none provided
@@ -339,10 +340,21 @@ def determine_concept(
339
340
  if debug:
340
341
  usage_metadata['determine_concept']["p_hash"] = encrypt_prompt(prompt)
341
342
 
342
- response_text = _get_response_text(response)
343
- candidate = response_text.strip()
343
+ # Try to parse as JSON first (with 'result' and 'reason' keys)
344
+ try:
345
+ parsed_response = _parse_json_from_llm_response(response)
346
+ if isinstance(parsed_response, dict) and 'result' in parsed_response:
347
+ candidate = parsed_response.get('result', '').strip()
348
+ identify_concept_reason = parsed_response.get('reason', None)
349
+ else:
350
+ # Fallback to plain text if JSON doesn't have expected structure
351
+ candidate = _get_response_text(response).strip()
352
+ except (json.JSONDecodeError, ValueError):
353
+ # If not JSON, treat as plain text (backwards compatibility)
354
+ candidate = _get_response_text(response).strip()
355
+
344
356
  if should_validate and candidate not in concepts_and_views.keys():
345
- error = f"Concept '{determined_concept_name}' not found in the list of concepts."
357
+ error = f"Concept '{candidate}' not found in the list of concepts."
346
358
  continue
347
359
 
348
360
  determined_concept_name = candidate
@@ -356,6 +368,7 @@ def determine_concept(
356
368
  return {
357
369
  "concept_metadata": concepts_and_views.get(determined_concept_name) if determined_concept_name else None,
358
370
  "concept": determined_concept_name,
371
+ "identify_concept_reason": identify_concept_reason,
359
372
  "schema": schema,
360
373
  "usage_metadata": usage_metadata,
361
374
  }
@@ -423,14 +436,45 @@ def _build_rel_columns_str(relationships: list[dict], columns_tags: Optional[dic
423
436
  return '.\n'.join(rel_str_arr) if rel_str_arr else ''
424
437
 
425
438
 
426
- def _parse_sql_from_llm_response(response: Any) -> str:
439
+ def _parse_sql_and_reason_from_llm_response(response: Any) -> dict:
440
+ """
441
+ Parse SQL & reason from LLM response. Handles both plain SQL strings and JSON format with 'result' and 'reason' keys.
442
+
443
+ Returns:
444
+ dict with 'sql' and 'reason' keys (reason may be None if not provided)
445
+ """
446
+ # Try to parse as JSON first
447
+ try:
448
+ parsed_json = _parse_json_from_llm_response(response)
449
+
450
+ # Extract SQL from 'result' key and reason from 'reason' key
451
+ if isinstance(parsed_json, dict) and 'result' in parsed_json:
452
+ sql = parsed_json.get('result', '')
453
+ reason = parsed_json.get('reason', None)
454
+
455
+ # Clean the SQL
456
+ sql = (sql
457
+ .replace("```sql", "")
458
+ .replace("```", "")
459
+ .replace('SELECT \n', 'SELECT ')
460
+ .replace(';', '')
461
+ .strip())
462
+
463
+ return {'sql': sql, 'reason': reason}
464
+ except (json.JSONDecodeError, ValueError):
465
+ # If not JSON, treat as plain SQL string (backwards compatibility)
466
+ pass
467
+
468
+ # Fallback to plain text parsing
427
469
  response_text = _get_response_text(response)
428
- return (response_text
429
- .replace("```sql", "")
430
- .replace("```", "")
431
- .replace('SELECT \n', 'SELECT ')
432
- .replace(';', '')
433
- .strip())
470
+ sql = (response_text
471
+ .replace("```sql", "")
472
+ .replace("```", "")
473
+ .replace('SELECT \n', 'SELECT ')
474
+ .replace(';', '')
475
+ .strip())
476
+
477
+ return {'sql': sql, 'reason': None}
434
478
 
435
479
 
436
480
  def _get_active_datasource(conn_params: dict) -> dict:
@@ -438,6 +482,38 @@ def _get_active_datasource(conn_params: dict) -> dict:
438
482
  return datasources[0] if datasources else None
439
483
 
440
484
 
485
+ def _parse_json_from_llm_response(response: Any) -> dict:
486
+ """
487
+ Parse JSON from LLM response. Handles markdown code blocks and extracts valid JSON.
488
+
489
+ Args:
490
+ response: LLM response object
491
+
492
+ Returns:
493
+ dict containing parsed JSON
494
+
495
+ Raises:
496
+ json.JSONDecodeError: If response cannot be parsed as JSON
497
+ ValueError: If response format is unexpected
498
+ """
499
+ response_text = _get_response_text(response)
500
+
501
+ # Remove markdown code block markers if present
502
+ content = response_text.strip()
503
+ if content.startswith("```json"):
504
+ content = content[7:] # Remove ```json
505
+ elif content.startswith("```"):
506
+ content = content[3:] # Remove ```
507
+
508
+ if content.endswith("```"):
509
+ content = content[:-3] # Remove closing ```
510
+
511
+ content = content.strip()
512
+
513
+ # Parse and return JSON
514
+ return json.loads(content)
515
+
516
+
441
517
  def _evaluate_sql_enable_reasoning(
442
518
  question: str,
443
519
  sql_query: str,
@@ -463,22 +539,8 @@ def _evaluate_sql_enable_reasoning(
463
539
 
464
540
  response = _call_llm_with_timeout(llm, prompt, timeout=timeout)
465
541
 
466
- # Extract JSON from response content (handle markdown code blocks)
467
- content = response.content.strip()
468
-
469
- # Remove markdown code block markers if present
470
- if content.startswith("```json"):
471
- content = content[7:] # Remove ```json
472
- elif content.startswith("```"):
473
- content = content[3:] # Remove ```
474
-
475
- if content.endswith("```"):
476
- content = content[:-3] # Remove closing ```
477
-
478
- content = content.strip()
479
-
480
542
  # Parse JSON response
481
- evaluation = json.loads(content)
543
+ evaluation = _parse_json_from_llm_response(response)
482
544
 
483
545
  return {
484
546
  "evaluation": evaluation,
@@ -571,11 +633,9 @@ def _build_sql_generation_context(
571
633
  def _generate_sql_with_llm(
572
634
  question: str,
573
635
  llm: LLM,
574
- conn_params: dict,
575
636
  generate_sql_prompt: Any,
576
637
  current_context: dict,
577
638
  note: str,
578
- should_validate_sql: bool,
579
639
  timeout: int,
580
640
  debug: bool = False,
581
641
  ) -> dict:
@@ -614,8 +674,12 @@ def _generate_sql_with_llm(
614
674
 
615
675
  response = _call_llm_with_timeout(llm, prompt, timeout=timeout)
616
676
 
677
+ # Parse response which now includes both SQL and reason
678
+ parsed_response = _parse_sql_and_reason_from_llm_response(response)
679
+
617
680
  result = {
618
- "sql": _parse_sql_from_llm_response(response),
681
+ "sql": parsed_response['sql'],
682
+ "generate_sql_reason": parsed_response['reason'],
619
683
  "apx_token_count": apx_token_count,
620
684
  "usage_metadata": _extract_usage_metadata(response),
621
685
  "is_valid": True,
@@ -625,11 +689,163 @@ def _generate_sql_with_llm(
625
689
  if debug:
626
690
  result["p_hash"] = encrypt_prompt(prompt)
627
691
 
628
- if should_validate_sql:
629
- result["is_valid"], result["error"], result["sql"] = validate_sql(result["sql"], conn_params)
630
692
 
631
693
  return result
632
694
 
695
+ def handle_generate_sql_reasoning(
696
+ sql_query: str,
697
+ question: str,
698
+ llm: LLM,
699
+ conn_params: dict,
700
+ schema: str,
701
+ concept: str,
702
+ concept_metadata: dict,
703
+ include_tags: bool,
704
+ exclude_properties: list,
705
+ db_is_case_sensitive: bool,
706
+ max_limit: int,
707
+ reasoning_steps: int,
708
+ note: str,
709
+ graph_depth: int,
710
+ usage_metadata: dict,
711
+ timeout: int,
712
+ debug: bool,
713
+ ) -> tuple[str, int, str]:
714
+ generate_sql_prompt = get_generate_sql_prompt_template(conn_params)
715
+ context_graph_depth = graph_depth
716
+ reasoned_sql = sql_query
717
+ reasoned_sql_reason = None
718
+ for step in range(reasoning_steps):
719
+ try:
720
+ # Step 1: Evaluate the current SQL
721
+ eval_result = _evaluate_sql_enable_reasoning(
722
+ question=question,
723
+ sql_query=reasoned_sql,
724
+ llm=llm,
725
+ conn_params=conn_params,
726
+ timeout=timeout,
727
+ )
728
+
729
+ usage_metadata[f'sql_reasoning_step_{step + 1}'] = {
730
+ "approximate": eval_result['apx_token_count'],
731
+ **eval_result['usage_metadata'],
732
+ }
733
+
734
+ evaluation = eval_result['evaluation']
735
+ reasoning_status = evaluation.get("assessment", "partial").lower()
736
+
737
+ if reasoning_status == "correct":
738
+ break
739
+
740
+ # Step 2: Regenerate SQL with feedback
741
+ evaluation_note = note + f"\n\nThe previously generated SQL: `{reasoned_sql}` was assessed as '{evaluation.get('assessment')}' because: {evaluation.get('reasoning', '*could not determine cause*')}. Please provide a corrected SQL query that better answers the question: '{question}'.\n\nCRITICAL: Return ONLY the SQL query without any explanation or comments."
742
+
743
+ # Increase graph depth for 2nd+ reasoning attempts, up to max of 3
744
+ context_graph_depth = min(3, int(graph_depth) + step) if graph_depth < 3 and step > 0 else graph_depth
745
+ regen_result = _generate_sql_with_llm(
746
+ question=question,
747
+ llm=llm,
748
+ generate_sql_prompt=generate_sql_prompt,
749
+ current_context=_build_sql_generation_context(
750
+ conn_params=conn_params,
751
+ schema=schema,
752
+ concept=concept,
753
+ concept_metadata=concept_metadata,
754
+ graph_depth=context_graph_depth,
755
+ include_tags=include_tags,
756
+ exclude_properties=exclude_properties,
757
+ db_is_case_sensitive=db_is_case_sensitive,
758
+ max_limit=max_limit),
759
+ note=evaluation_note,
760
+ timeout=timeout,
761
+ debug=debug,
762
+ )
763
+
764
+ reasoned_sql = regen_result['sql']
765
+ reasoned_sql_reason = regen_result['generate_sql_reason']
766
+ error = regen_result['error']
767
+
768
+ step_key = f'generate_sql_reasoning_step_{step + 1}'
769
+ usage_metadata[step_key] = {
770
+ "approximate": regen_result['apx_token_count'],
771
+ **regen_result['usage_metadata'],
772
+ }
773
+ if debug and 'p_hash' in regen_result:
774
+ usage_metadata[step_key]['p_hash'] = regen_result['p_hash']
775
+
776
+ if error:
777
+ raise Exception(error)
778
+
779
+ except TimeoutError as e:
780
+ raise Exception(f"LLM call timed out: {str(e)}")
781
+ except Exception as e:
782
+ print(f"Warning: LLM reasoning failed: {e}")
783
+ break
784
+
785
+ return reasoned_sql, context_graph_depth, reasoned_sql_reason
786
+
787
+ def handle_validate_generate_sql(
788
+ sql_query: str,
789
+ question: str,
790
+ llm: LLM,
791
+ conn_params: dict,
792
+ generate_sql_prompt: Any,
793
+ schema: str,
794
+ concept: str,
795
+ concept_metadata: dict,
796
+ include_tags: bool,
797
+ exclude_properties: list,
798
+ db_is_case_sensitive: bool,
799
+ max_limit: int,
800
+ graph_depth: int,
801
+ retries: int,
802
+ timeout: int,
803
+ debug: bool,
804
+ usage_metadata: dict,
805
+ ) -> tuple[bool, str, str]:
806
+ is_sql_valid, error, sql_query = validate_sql(sql_query, conn_params)
807
+ validation_attempt = 0
808
+
809
+ while validation_attempt < retries and not is_sql_valid:
810
+ validation_attempt += 1
811
+ validation_err_txt = f"\nThe generated SQL (`{sql_query}`) was invalid with error: {error}. Please generate a corrected query that achieves the intended result." if error and "snowflake" not in llm._llm_type else ""
812
+
813
+ regen_result = _generate_sql_with_llm(
814
+ question=question,
815
+ llm=llm,
816
+ generate_sql_prompt=generate_sql_prompt,
817
+ current_context=_build_sql_generation_context(
818
+ conn_params=conn_params,
819
+ schema=schema,
820
+ concept=concept,
821
+ concept_metadata=concept_metadata,
822
+ graph_depth=graph_depth,
823
+ include_tags=include_tags,
824
+ exclude_properties=exclude_properties,
825
+ db_is_case_sensitive=db_is_case_sensitive,
826
+ max_limit=max_limit),
827
+ note=validation_err_txt,
828
+ timeout=timeout,
829
+ debug=debug,
830
+ )
831
+
832
+ regen_error = regen_result['error']
833
+ sql_query = regen_result['sql']
834
+
835
+ validation_key = f'generate_sql_validation_regen_{validation_attempt}'
836
+ usage_metadata[validation_key] = {
837
+ "approximate": regen_result['apx_token_count'],
838
+ **regen_result['usage_metadata'],
839
+ }
840
+ if debug and 'p_hash' in regen_result:
841
+ usage_metadata[validation_key]['p_hash'] = regen_result['p_hash']
842
+
843
+ if regen_error:
844
+ raise Exception(regen_error)
845
+
846
+ is_sql_valid, error, sql_query = validate_sql(sql_query, conn_params)
847
+
848
+ return is_sql_valid, error, sql_query
633
849
 
634
850
  def generate_sql(
635
851
  question: str,
@@ -656,13 +872,11 @@ def generate_sql(
656
872
  usage_metadata = {}
657
873
  concept_metadata = None
658
874
  reasoning_status = 'correct'
659
-
875
+
660
876
  # Use config default timeout if none provided
661
877
  if timeout is None:
662
878
  timeout = config.llm_timeout
663
879
 
664
- generate_sql_prompt = get_generate_sql_prompt_template(conn_params)
665
-
666
880
  if concept and concept != "" and (schema is None or schema != "vtimbr"):
667
881
  concepts_list = [concept]
668
882
  elif concept and concept != "" and schema == "vtimbr":
@@ -682,154 +896,105 @@ def generate_sql(
682
896
  debug=debug,
683
897
  timeout=timeout,
684
898
  )
685
- concept, schema, concept_metadata = determine_concept_res.get('concept'), determine_concept_res.get('schema'), determine_concept_res.get('concept_metadata')
899
+
900
+ concept = determine_concept_res.get('concept')
901
+ identify_concept_reason = determine_concept_res.get('identify_concept_reason', None)
902
+ schema = determine_concept_res.get('schema')
903
+ concept_metadata = determine_concept_res.get('concept_metadata')
686
904
  usage_metadata.update(determine_concept_res.get('usage_metadata', {}))
687
905
 
688
906
  if not concept:
689
907
  raise Exception("No relevant concept found for the query.")
690
908
 
909
+ generate_sql_prompt = get_generate_sql_prompt_template(conn_params)
691
910
  sql_query = None
692
- iteration = 0
693
- is_sql_valid = True
911
+ generate_sql_reason = None
912
+ is_sql_valid = True # Assume valid by default; set to False only if validation fails
694
913
  error = ''
695
- while sql_query is None or (should_validate_sql and iteration < retries and not is_sql_valid):
696
- iteration += 1
697
- err_txt = f"\nThe original SQL (`{sql_query}`) was invalid with error: {error}. Please generate a corrected query." if error and "snowflake" not in llm._llm_type else ""
698
914
 
699
- try:
700
- result = _generate_sql_with_llm(
915
+ try:
916
+ result = _generate_sql_with_llm(
917
+ question=question,
918
+ llm=llm,
919
+ generate_sql_prompt=generate_sql_prompt,
920
+ current_context=_build_sql_generation_context(
921
+ conn_params=conn_params,
922
+ schema=schema,
923
+ concept=concept,
924
+ concept_metadata=concept_metadata,
925
+ graph_depth=graph_depth,
926
+ include_tags=include_tags,
927
+ exclude_properties=exclude_properties,
928
+ db_is_case_sensitive=db_is_case_sensitive,
929
+ max_limit=max_limit),
930
+ note=note,
931
+ timeout=timeout,
932
+ debug=debug,
933
+ )
934
+
935
+ usage_metadata['generate_sql'] = {
936
+ "approximate": result['apx_token_count'],
937
+ **result['usage_metadata'],
938
+ }
939
+ if debug and 'p_hash' in result:
940
+ usage_metadata['generate_sql']["p_hash"] = result['p_hash']
941
+
942
+ sql_query = result['sql']
943
+ generate_sql_reason = result.get('generate_sql_reason', None)
944
+ error = result['error']
945
+
946
+ if error:
947
+ raise Exception(error)
948
+
949
+ if enable_reasoning and sql_query is not None:
950
+ sql_query, graph_depth, generate_sql_reason = handle_generate_sql_reasoning(
951
+ sql_query=sql_query,
952
+ question=question,
953
+ llm=llm,
954
+ conn_params=conn_params,
955
+ schema=schema,
956
+ concept=concept,
957
+ concept_metadata=concept_metadata,
958
+ include_tags=include_tags,
959
+ exclude_properties=exclude_properties,
960
+ db_is_case_sensitive=db_is_case_sensitive,
961
+ max_limit=max_limit,
962
+ reasoning_steps=reasoning_steps,
963
+ note=note,
964
+ graph_depth=graph_depth,
965
+ usage_metadata=usage_metadata,
966
+ timeout=timeout,
967
+ debug=debug,
968
+ )
969
+
970
+ if should_validate_sql or enable_reasoning:
971
+ # Validate & regenerate only once if reasoning enabled and validation is disabled
972
+ validate_retries = 1 if not should_validate_sql else retries
973
+ is_sql_valid, error, sql_query = handle_validate_generate_sql(
974
+ sql_query=sql_query,
701
975
  question=question,
702
976
  llm=llm,
703
977
  conn_params=conn_params,
704
978
  generate_sql_prompt=generate_sql_prompt,
705
- current_context=_build_sql_generation_context(
706
- conn_params=conn_params,
707
- schema=schema,
708
- concept=concept,
709
- concept_metadata=concept_metadata,
710
- graph_depth=graph_depth,
711
- include_tags=include_tags,
712
- exclude_properties=exclude_properties,
713
- db_is_case_sensitive=db_is_case_sensitive,
714
- max_limit=max_limit),
715
- note=note + err_txt,
716
- should_validate_sql=should_validate_sql,
979
+ schema=schema,
980
+ concept=concept,
981
+ concept_metadata=concept_metadata,
982
+ include_tags=include_tags,
983
+ exclude_properties=exclude_properties,
984
+ db_is_case_sensitive=db_is_case_sensitive,
985
+ max_limit=max_limit,
986
+ graph_depth=graph_depth,
987
+ retries=validate_retries,
717
988
  timeout=timeout,
718
989
  debug=debug,
990
+ usage_metadata=usage_metadata,
719
991
  )
720
-
721
- usage_metadata['generate_sql'] = {
722
- "approximate": result['apx_token_count'],
723
- **result['usage_metadata'],
724
- }
725
- if debug and 'p_hash' in result:
726
- usage_metadata['generate_sql']["p_hash"] = result['p_hash']
727
-
728
- sql_query = result['sql']
729
- is_sql_valid = result['is_valid']
730
- error = result['error']
731
-
732
- except TimeoutError as e:
733
- error = f"LLM call timed out: {str(e)}"
734
- raise Exception(error)
735
- except Exception as e:
736
- error = f"LLM call failed: {str(e)}"
737
- if should_validate_sql:
738
- continue
739
- else:
740
- raise Exception(error)
741
-
742
-
743
- if enable_reasoning and sql_query is not None:
744
- for step in range(reasoning_steps):
745
- try:
746
- # Step 1: Evaluate the current SQL
747
- eval_result = _evaluate_sql_enable_reasoning(
748
- question=question,
749
- sql_query=sql_query,
750
- llm=llm,
751
- conn_params=conn_params,
752
- timeout=timeout,
753
- )
754
-
755
- usage_metadata[f'sql_reasoning_step_{step + 1}'] = {
756
- "approximate": eval_result['apx_token_count'],
757
- **eval_result['usage_metadata'],
758
- }
759
-
760
- evaluation = eval_result['evaluation']
761
- reasoning_status = evaluation.get("assessment", "partial").lower()
762
-
763
- if reasoning_status == "correct":
764
- break
765
-
766
- # Step 2: Regenerate SQL with feedback (with validation retries)
767
- evaluation_note = note + f"\n\nThe previously generated SQL: `{sql_query}` was assessed as '{evaluation.get('assessment')}' because: {evaluation.get('reasoning', '*could not determine cause*')}. Please provide a corrected SQL query that better answers the question: '{question}'.\n\nCRITICAL: Return ONLY the SQL query without any explanation or comments."
768
-
769
- # Increase graph depth for 2nd+ reasoning attempts, up to max of 3
770
- context_graph_depth = min(3, int(graph_depth) + step) if graph_depth < 3 and step > 0 else graph_depth
771
-
772
- # Regenerate SQL with validation retries
773
- # Always validate during reasoning to ensure quality, regardless of global should_validate_sql flag
774
- validation_iteration = 0
775
- regen_is_valid = False
776
- regen_error = ''
777
- regen_sql = None
778
-
779
- while validation_iteration < retries and (regen_sql is None or not regen_is_valid):
780
- validation_iteration += 1
781
- validation_err_txt = f"\nThe regenerated SQL (`{regen_sql}`) was invalid with error: {regen_error}. Please generate a corrected query." if regen_error and "snowflake" not in llm._llm_type else ""
782
-
783
- regen_result = _generate_sql_with_llm(
784
- question=question,
785
- llm=llm,
786
- conn_params=conn_params,
787
- generate_sql_prompt=generate_sql_prompt,
788
- current_context=_build_sql_generation_context(
789
- conn_params=conn_params,
790
- schema=schema,
791
- concept=concept,
792
- concept_metadata=concept_metadata,
793
- graph_depth=context_graph_depth,
794
- include_tags=include_tags,
795
- exclude_properties=exclude_properties,
796
- db_is_case_sensitive=db_is_case_sensitive,
797
- max_limit=max_limit),
798
- note=evaluation_note + validation_err_txt,
799
- should_validate_sql=True, # Always validate during reasoning
800
- timeout=timeout,
801
- debug=debug,
802
- )
803
-
804
- regen_sql = regen_result['sql']
805
- regen_is_valid = regen_result['is_valid']
806
- regen_error = regen_result['error']
807
-
808
- # Track token usage for each validation iteration
809
- if validation_iteration == 1:
810
- usage_metadata[f'generate_sql_reasoning_step_{step + 1}'] = {
811
- "approximate": regen_result['apx_token_count'],
812
- **regen_result['usage_metadata'],
813
- }
814
- if debug and 'p_hash' in regen_result:
815
- usage_metadata[f'generate_sql_reasoning_step_{step + 1}']['p_hash'] = regen_result['p_hash']
816
- else:
817
- usage_metadata[f'generate_sql_reasoning_step_{step + 1}_validation_{validation_iteration}'] = {
818
- "approximate": regen_result['apx_token_count'],
819
- **regen_result['usage_metadata'],
820
- }
821
- if debug and 'p_hash' in regen_result:
822
- usage_metadata[f'generate_sql_reasoning_step_{step + 1}_validation_{validation_iteration}']['p_hash'] = regen_result['p_hash']
823
-
824
- sql_query = regen_sql
825
- is_sql_valid = regen_is_valid
826
- error = regen_error
827
-
828
- except TimeoutError as e:
829
- raise Exception(f"LLM call timed out: {str(e)}")
830
- except Exception as e:
831
- print(f"Warning: LLM reasoning failed: {e}")
832
- break
992
+ except TimeoutError as e:
993
+ error = f"LLM call timed out: {str(e)}"
994
+ raise Exception(error)
995
+ except Exception as e:
996
+ error = f"LLM call failed: {str(e)}"
997
+ raise Exception(error)
833
998
 
834
999
  return {
835
1000
  "sql": sql_query,
@@ -837,6 +1002,8 @@ def generate_sql(
837
1002
  "schema": schema,
838
1003
  "error": error if not is_sql_valid else None,
839
1004
  "is_sql_valid": is_sql_valid if should_validate_sql else None,
1005
+ "identify_concept_reason": identify_concept_reason,
1006
+ "generate_sql_reason": generate_sql_reason,
840
1007
  "reasoning_status": reasoning_status,
841
1008
  "usage_metadata": usage_metadata,
842
1009
  }
@@ -154,6 +154,33 @@ def get_datasources(conn_params: dict, filter_active: Optional[bool] = False) ->
154
154
  return res
155
155
 
156
156
 
157
+ def get_timbr_agent_options(agent_name: str, conn_params: dict) -> dict:
158
+ """
159
+ Get all options for a specific agent from timbr sys_agents_options table.
160
+
161
+ Args:
162
+ agent_name: Name of the agent to get options for
163
+ conn_params: Connection parameters for Timbr system engine
164
+
165
+ Returns:
166
+ Dictionary of option_name -> option_value pairs
167
+ """
168
+ options = {}
169
+
170
+ # Query agent options (case-insensitive match on agent_name)
171
+ options_query = f"SELECT option_name, option_value FROM timbr.sys_agents_options WHERE LOWER(agent_name) = LOWER('{agent_name}')"
172
+
173
+ results = run_query(options_query, conn_params)
174
+ if len(results) == 0:
175
+ raise Exception(f'Agent "{agent_name}" not found or has no options defined.')
176
+ for row in results:
177
+ option_name = row.get('option_name')
178
+ option_value = row.get('option_value', '')
179
+ options[option_name] = option_value
180
+
181
+ return options
182
+
183
+
157
184
  def _validate(sql: str, conn_params: dict) -> bool:
158
185
  explain_sql = f"EXPLAIN {sql}"
159
186
  explain_res = run_query(explain_sql, conn_params)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-timbr
3
- Version: 2.1.14
3
+ Version: 3.0.0
4
4
  Summary: LangChain & LangGraph extensions that parse LLM prompts into Timbr semantic SQL and execute them.
5
5
  Project-URL: Homepage, https://github.com/WPSemantix/langchain-timbr
6
6
  Project-URL: Documentation, https://docs.timbr.ai/doc/docs/integration/langchain-sdk/
@@ -0,0 +1,28 @@
1
+ langchain_timbr/__init__.py,sha256=qNyk3Rt-8oWr_OGuU_E-6siNZXuCnvVEkj65EIuVbbQ,824
2
+ langchain_timbr/_version.py,sha256=9fL11DDeXfhKmxyz_HrYU3Yy7BjMWrJklCddPlmAj6A,704
3
+ langchain_timbr/config.py,sha256=Y0fTwkhLj8M-vORdVCAFykrSFIifDovV6BETfdIKZ9A,1973
4
+ langchain_timbr/timbr_llm_connector.py,sha256=Y3nzWoocI5txvPGPAxwFsJde9k9l2J9ioB54MYRLrEQ,13288
5
+ langchain_timbr/langchain/__init__.py,sha256=ejcsZKP9PK0j4WrrCCcvBXpDpP-TeRiVb21OIUJqix8,580
6
+ langchain_timbr/langchain/execute_timbr_query_chain.py,sha256=UIUQR8adnWmGCrx6t1nnMrD8yoejgUMr80bf3bl2xUw,20251
7
+ langchain_timbr/langchain/generate_answer_chain.py,sha256=fGq4Ls6cuHBzTCntl1kLHuCp43vEssDkWZA_gPTcm4E,5415
8
+ langchain_timbr/langchain/generate_timbr_sql_chain.py,sha256=mb8VHTpq-Cw2UqDPDE-Z7dmJ7xtInktkN5nr6N9206g,13150
9
+ langchain_timbr/langchain/identify_concept_chain.py,sha256=ga91QnuaXXxVAbx5Cz32jcq-PXby4zZacil2Kr-GTqo,8699
10
+ langchain_timbr/langchain/timbr_sql_agent.py,sha256=Uxvw742o6478LvIIQUVK64tBYzDWObj6DBwWGMi0bi8,21895
11
+ langchain_timbr/langchain/validate_timbr_sql_chain.py,sha256=Z1FfykDDba1lPSTN0J0Y2Fhxe2sOFqV9Y8-rudsTeJQ,13650
12
+ langchain_timbr/langgraph/__init__.py,sha256=mKBFd0x01jWpRujUWe-suX3FFhenPoDxrvzs8I0mum0,457
13
+ langchain_timbr/langgraph/execute_timbr_query_node.py,sha256=1JB8pMkSeBGxDMd64OXjZUUjvizILJN8bkz6ibbpgE8,6229
14
+ langchain_timbr/langgraph/generate_response_node.py,sha256=V1XvbmkRBiJ_cExGpWXsBEUxvlLQA3hsVlsg6NHbD94,2504
15
+ langchain_timbr/langgraph/generate_timbr_sql_node.py,sha256=BKFbvf9_YJmpfmCYPTpDm7z6Lvy0regmYGmNznPMl_0,5563
16
+ langchain_timbr/langgraph/identify_concept_node.py,sha256=APhgyNW9YiG1d7lxiZWyFxABHo6vy8sQaXCDRnSPL8w,3821
17
+ langchain_timbr/langgraph/validate_timbr_query_node.py,sha256=eFAIryr-oVrgbbtVHNQTR6md2XS_3cmC8pLgYQ89BiE,5401
18
+ langchain_timbr/llm_wrapper/llm_wrapper.py,sha256=j94DqIGECXyfAVayLC7VaNxs_8n1qYFiHY2Qvt2B3Bc,17537
19
+ langchain_timbr/llm_wrapper/timbr_llm_wrapper.py,sha256=sDqDOz0qu8b4WWlagjNceswMVyvEJ8yBWZq2etBh-T0,1362
20
+ langchain_timbr/utils/general.py,sha256=KkehHvIj8GoQ_0KVXLcUVeaYaTtkuzgXmYYx2TXJhI4,10253
21
+ langchain_timbr/utils/prompt_service.py,sha256=QVmfA9cHO2IPVsKG8V5cuMm2gPfvRq2VzLcx04sqT88,12197
22
+ langchain_timbr/utils/temperature_supported_models.json,sha256=d3UmBUpG38zDjjB42IoGpHTUaf0pHMBRSPY99ao1a3g,1832
23
+ langchain_timbr/utils/timbr_llm_utils.py,sha256=iuCPwEOmd0b5N8LNO3IV7K3qozlalUXvh_0QD8bHH28,41001
24
+ langchain_timbr/utils/timbr_utils.py,sha256=olSwn_BYalkhiHR87zfgjosxxGwtMmsrQTqAtu64kPs,20603
25
+ langchain_timbr-3.0.0.dist-info/METADATA,sha256=vvI-waiUk2ZNRxM791b5zpIIvap8xnCFcwjONW8kZkE,10767
26
+ langchain_timbr-3.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
27
+ langchain_timbr-3.0.0.dist-info/licenses/LICENSE,sha256=0ITGFk2alkC7-e--bRGtuzDrv62USIiVyV2Crf3_L_0,1065
28
+ langchain_timbr-3.0.0.dist-info/RECORD,,