MindsDB 25.9.2.0a1__py3-none-any.whl → 25.9.3rc1__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.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (116) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +39 -20
  3. mindsdb/api/a2a/agent.py +7 -9
  4. mindsdb/api/a2a/common/server/server.py +3 -3
  5. mindsdb/api/a2a/common/server/task_manager.py +4 -4
  6. mindsdb/api/a2a/task_manager.py +15 -17
  7. mindsdb/api/common/middleware.py +9 -11
  8. mindsdb/api/executor/command_executor.py +2 -4
  9. mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
  10. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +100 -48
  11. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
  12. mindsdb/api/executor/datahub/datanodes/system_tables.py +1 -1
  13. mindsdb/api/executor/exceptions.py +29 -10
  14. mindsdb/api/executor/planner/plan_join.py +17 -3
  15. mindsdb/api/executor/sql_query/sql_query.py +74 -74
  16. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
  17. mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
  18. mindsdb/api/executor/utilities/functions.py +6 -6
  19. mindsdb/api/executor/utilities/sql.py +32 -16
  20. mindsdb/api/http/gui.py +5 -11
  21. mindsdb/api/http/initialize.py +8 -10
  22. mindsdb/api/http/namespaces/agents.py +10 -12
  23. mindsdb/api/http/namespaces/analysis.py +13 -20
  24. mindsdb/api/http/namespaces/auth.py +1 -1
  25. mindsdb/api/http/namespaces/config.py +15 -11
  26. mindsdb/api/http/namespaces/databases.py +140 -201
  27. mindsdb/api/http/namespaces/file.py +15 -4
  28. mindsdb/api/http/namespaces/handlers.py +7 -2
  29. mindsdb/api/http/namespaces/knowledge_bases.py +8 -7
  30. mindsdb/api/http/namespaces/models.py +94 -126
  31. mindsdb/api/http/namespaces/projects.py +13 -22
  32. mindsdb/api/http/namespaces/sql.py +33 -25
  33. mindsdb/api/http/namespaces/tab.py +27 -37
  34. mindsdb/api/http/namespaces/views.py +1 -1
  35. mindsdb/api/http/start.py +14 -8
  36. mindsdb/api/mcp/__init__.py +2 -1
  37. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
  38. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
  39. mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
  40. mindsdb/api/postgres/postgres_proxy/executor/executor.py +6 -13
  41. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +40 -28
  42. mindsdb/integrations/handlers/byom_handler/byom_handler.py +168 -185
  43. mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
  44. mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
  45. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +13 -1
  46. mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
  47. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
  48. mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
  49. mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
  50. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
  51. mindsdb/integrations/libs/api_handler.py +10 -10
  52. mindsdb/integrations/libs/base.py +4 -4
  53. mindsdb/integrations/libs/llm/utils.py +2 -2
  54. mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
  55. mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
  56. mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
  57. mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
  58. mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
  59. mindsdb/integrations/libs/process_cache.py +132 -140
  60. mindsdb/integrations/libs/response.py +18 -12
  61. mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
  62. mindsdb/integrations/utilities/files/file_reader.py +6 -7
  63. mindsdb/integrations/utilities/rag/config_loader.py +37 -26
  64. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +59 -9
  65. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
  66. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
  67. mindsdb/integrations/utilities/rag/settings.py +58 -133
  68. mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
  69. mindsdb/interfaces/agents/agents_controller.py +2 -1
  70. mindsdb/interfaces/agents/constants.py +0 -2
  71. mindsdb/interfaces/agents/litellm_server.py +34 -58
  72. mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
  73. mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
  74. mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
  75. mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
  76. mindsdb/interfaces/chatbot/polling.py +30 -18
  77. mindsdb/interfaces/data_catalog/data_catalog_loader.py +10 -10
  78. mindsdb/interfaces/database/integrations.py +19 -2
  79. mindsdb/interfaces/file/file_controller.py +6 -6
  80. mindsdb/interfaces/functions/controller.py +1 -1
  81. mindsdb/interfaces/functions/to_markdown.py +2 -2
  82. mindsdb/interfaces/jobs/jobs_controller.py +5 -5
  83. mindsdb/interfaces/jobs/scheduler.py +3 -8
  84. mindsdb/interfaces/knowledge_base/controller.py +50 -23
  85. mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
  86. mindsdb/interfaces/model/model_controller.py +170 -166
  87. mindsdb/interfaces/query_context/context_controller.py +14 -2
  88. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +6 -4
  89. mindsdb/interfaces/skills/retrieval_tool.py +43 -50
  90. mindsdb/interfaces/skills/skill_tool.py +2 -2
  91. mindsdb/interfaces/skills/sql_agent.py +25 -19
  92. mindsdb/interfaces/storage/fs.py +114 -169
  93. mindsdb/interfaces/storage/json.py +19 -18
  94. mindsdb/interfaces/tabs/tabs_controller.py +49 -72
  95. mindsdb/interfaces/tasks/task_monitor.py +3 -9
  96. mindsdb/interfaces/tasks/task_thread.py +7 -9
  97. mindsdb/interfaces/triggers/trigger_task.py +7 -13
  98. mindsdb/interfaces/triggers/triggers_controller.py +47 -50
  99. mindsdb/migrations/migrate.py +16 -16
  100. mindsdb/utilities/api_status.py +58 -0
  101. mindsdb/utilities/config.py +49 -0
  102. mindsdb/utilities/exception.py +40 -1
  103. mindsdb/utilities/fs.py +0 -1
  104. mindsdb/utilities/hooks/profiling.py +17 -14
  105. mindsdb/utilities/langfuse.py +40 -45
  106. mindsdb/utilities/log.py +272 -0
  107. mindsdb/utilities/ml_task_queue/consumer.py +52 -58
  108. mindsdb/utilities/ml_task_queue/producer.py +26 -30
  109. mindsdb/utilities/render/sqlalchemy_render.py +7 -6
  110. mindsdb/utilities/utils.py +2 -2
  111. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/METADATA +269 -264
  112. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/RECORD +115 -115
  113. mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
  114. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/WHEEL +0 -0
  115. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/licenses/LICENSE +0 -0
  116. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from alembic.command import upgrade, autogen # noqa
3
+ from alembic.command import upgrade, autogen # noqa
4
4
  from alembic.config import Config
5
5
  from alembic.script import ScriptDirectory
6
6
  from alembic.script.revision import ResolutionError
@@ -15,20 +15,19 @@ logger = log.getLogger(__name__)
15
15
 
16
16
  # This is a migration that is like a 'base version'. Applying only this
17
17
  # migration to a fresh DB is equivalent to applying all previous migrations.
18
- current_checkpoint = '9f150e4f9a05'
18
+ current_checkpoint = "9f150e4f9a05"
19
19
 
20
20
 
21
21
  def apply_checkpoint_migration(script) -> None:
22
- """Apply the checkpoint migration to the database.
23
- """
22
+ """Apply the checkpoint migration to the database."""
24
23
  with db.engine.begin() as connection:
25
24
  context = MigrationContext.configure(
26
25
  connection,
27
26
  opts={
28
- 'as_sql': False,
29
- 'starting_rev': None, # ignore current version
30
- 'destination_rev': current_checkpoint,
31
- }
27
+ "as_sql": False,
28
+ "starting_rev": None, # ignore current version
29
+ "destination_rev": current_checkpoint,
30
+ },
32
31
  )
33
32
  revision = script.get_revision(current_checkpoint)
34
33
  if not revision:
@@ -41,7 +40,7 @@ def apply_checkpoint_migration(script) -> None:
41
40
 
42
41
 
43
42
  def get_current_revision() -> str | None:
44
- """ Get the current revision of the database.
43
+ """Get the current revision of the database.
45
44
 
46
45
  Returns:
47
46
  str | None: The current revision of the database.
@@ -52,17 +51,18 @@ def get_current_revision() -> str | None:
52
51
 
53
52
 
54
53
  def migrate_to_head():
55
- """ Trying to update database to head revision.
56
- If alembic unable to recognize current revision (In case when database version is newer than backend)
57
- then do nothing.
54
+ """Trying to update database to head revision.
55
+ If alembic unable to recognize current revision (In case when database version is newer than backend)
56
+ then do nothing.
58
57
  """
58
+ logger.debug("Applying database migrations")
59
59
 
60
- config_file = Path(__file__).parent / 'alembic.ini'
60
+ config_file = Path(__file__).parent / "alembic.ini"
61
61
  config = Config(config_file)
62
62
 
63
63
  # mindsdb can runs not from project directory
64
- script_location_abc = config_file.parent / config.get_main_option('script_location')
65
- config.set_main_option('script_location', str(script_location_abc))
64
+ script_location_abc = config_file.parent / config.get_main_option("script_location")
65
+ config.set_main_option("script_location", str(script_location_abc))
66
66
 
67
67
  script = ScriptDirectory.from_config(config)
68
68
  cur_revision = get_current_revision()
@@ -81,7 +81,7 @@ def migrate_to_head():
81
81
  return
82
82
 
83
83
  logger.info("Migrations are available. Applying updates to the database.")
84
- upgrade(config=config, revision='head')
84
+ upgrade(config=config, revision="head")
85
85
 
86
86
 
87
87
  if __name__ == "__main__":
@@ -0,0 +1,58 @@
1
+ import os
2
+ import json
3
+
4
+ from mindsdb.utilities.config import config
5
+ from mindsdb.utilities import log
6
+
7
+
8
+ logger = log.getLogger(__name__)
9
+ _api_status_file = None
10
+
11
+
12
+ def _get_api_status_file():
13
+ global _api_status_file
14
+ if _api_status_file is None:
15
+ # Use a temporary file that can be shared across processes.
16
+ temp_dir = config["paths"]["tmp"]
17
+ _api_status_file = os.path.join(temp_dir, "mindsdb_api_status.json")
18
+ # Overwrite the file if it exists.
19
+ if os.path.exists(_api_status_file):
20
+ try:
21
+ os.remove(_api_status_file)
22
+ except OSError:
23
+ logger.exception(f"Error removing existing API status file: {_api_status_file}")
24
+
25
+ return _api_status_file
26
+
27
+
28
+ def get_api_status():
29
+ """Get the current API status from the shared file."""
30
+ status_file = _get_api_status_file()
31
+ try:
32
+ if os.path.exists(status_file):
33
+ with open(status_file, "r") as f:
34
+ return json.load(f)
35
+ except (json.JSONDecodeError, IOError):
36
+ pass
37
+ return {}
38
+
39
+
40
+ def set_api_status(api_name: str, status: bool):
41
+ """Set the status of an API in the shared file."""
42
+ status_file = _get_api_status_file()
43
+ current_status = get_api_status()
44
+ current_status[api_name] = status
45
+
46
+ # Write atomically to avoid race conditions.
47
+ temp_file = status_file + ".tmp"
48
+ try:
49
+ with open(temp_file, "w") as f:
50
+ json.dump(current_status, f)
51
+ os.replace(temp_file, status_file)
52
+ except IOError:
53
+ # Clean up temp file if it exists.
54
+ if os.path.exists(temp_file):
55
+ try:
56
+ os.remove(temp_file)
57
+ except OSError:
58
+ pass
@@ -170,6 +170,7 @@ class Config:
170
170
  "restart_on_failure": True,
171
171
  "max_restart_count": 1,
172
172
  "max_restart_interval_seconds": 60,
173
+ "a2wsgi": {"workers": 10, "send_queue_size": 10},
173
174
  },
174
175
  "mysql": {
175
176
  "host": api_host,
@@ -300,6 +301,54 @@ class Config:
300
301
  self._env_config["default_reranking_model"] = {
301
302
  "api_key": os.environ["MINDSDB_DEFAULT_RERANKING_MODEL_API_KEY"]
302
303
  }
304
+
305
+ # Reranker configuration from environment variables
306
+ reranker_config = {}
307
+ if os.environ.get("MINDSDB_RERANKER_N", "") != "":
308
+ try:
309
+ reranker_config["n"] = int(os.environ["MINDSDB_RERANKER_N"])
310
+ except ValueError:
311
+ raise ValueError(f"MINDSDB_RERANKER_N must be an integer, got: {os.environ['MINDSDB_RERANKER_N']}")
312
+
313
+ if os.environ.get("MINDSDB_RERANKER_LOGPROBS", "") != "":
314
+ logprobs_value = os.environ["MINDSDB_RERANKER_LOGPROBS"].lower()
315
+ if logprobs_value in ("true", "1", "yes", "y"):
316
+ reranker_config["logprobs"] = True
317
+ elif logprobs_value in ("false", "0", "no", "n"):
318
+ reranker_config["logprobs"] = False
319
+ else:
320
+ raise ValueError(
321
+ f"MINDSDB_RERANKER_LOGPROBS must be a boolean value, got: {os.environ['MINDSDB_RERANKER_LOGPROBS']}"
322
+ )
323
+
324
+ if os.environ.get("MINDSDB_RERANKER_TOP_LOGPROBS", "") != "":
325
+ try:
326
+ reranker_config["top_logprobs"] = int(os.environ["MINDSDB_RERANKER_TOP_LOGPROBS"])
327
+ except ValueError:
328
+ raise ValueError(
329
+ f"MINDSDB_RERANKER_TOP_LOGPROBS must be an integer, got: {os.environ['MINDSDB_RERANKER_TOP_LOGPROBS']}"
330
+ )
331
+
332
+ if os.environ.get("MINDSDB_RERANKER_MAX_TOKENS", "") != "":
333
+ try:
334
+ reranker_config["max_tokens"] = int(os.environ["MINDSDB_RERANKER_MAX_TOKENS"])
335
+ except ValueError:
336
+ raise ValueError(
337
+ f"MINDSDB_RERANKER_MAX_TOKENS must be an integer, got: {os.environ['MINDSDB_RERANKER_MAX_TOKENS']}"
338
+ )
339
+
340
+ if os.environ.get("MINDSDB_RERANKER_VALID_CLASS_TOKENS", "") != "":
341
+ try:
342
+ reranker_config["valid_class_tokens"] = os.environ["MINDSDB_RERANKER_VALID_CLASS_TOKENS"].split(",")
343
+ except ValueError:
344
+ raise ValueError(
345
+ f"MINDSDB_RERANKER_VALID_CLASS_TOKENS must be a comma-separated list of strings, got: {os.environ['MINDSDB_RERANKER_VALID_CLASS_TOKENS']}"
346
+ )
347
+
348
+ if reranker_config:
349
+ if "default_reranking_model" not in self._env_config:
350
+ self._env_config["default_reranking_model"] = {}
351
+ self._env_config["default_reranking_model"].update(reranker_config)
303
352
  if os.environ.get("MINDSDB_DATA_CATALOG_ENABLED", "").lower() in ("1", "true"):
304
353
  self._env_config["data_catalog"] = {"enabled": True}
305
354
 
@@ -1,7 +1,14 @@
1
1
  from textwrap import indent
2
2
 
3
3
 
4
- class BaseEntityException(Exception):
4
+ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import ERR
5
+
6
+
7
+ class MindsDBError(Exception):
8
+ pass
9
+
10
+
11
+ class BaseEntityException(MindsDBError):
5
12
  """Base exception for entitys errors
6
13
 
7
14
  Attributes:
@@ -35,6 +42,38 @@ class EntityNotExistsError(BaseEntityException):
35
42
  super().__init__(message, entity_name)
36
43
 
37
44
 
45
+ class ParsingError(MindsDBError):
46
+ pass
47
+
48
+
49
+ class QueryError(MindsDBError):
50
+ def __init__(
51
+ self,
52
+ db_name: str | None = None,
53
+ db_type: str | None = None,
54
+ db_error_msg: str | None = None,
55
+ failed_query: str | None = None,
56
+ is_external: bool = True,
57
+ is_acceptable: bool = False,
58
+ ) -> None:
59
+ self.mysql_error_code = ERR.ER_UNKNOWN_ERROR
60
+ self.db_name = db_name
61
+ self.db_type = db_type
62
+ self.db_error_msg = db_error_msg
63
+ self.failed_query = failed_query
64
+ self.is_external = is_external
65
+ self.is_acceptable = is_acceptable
66
+
67
+ def __str__(self) -> str:
68
+ return format_db_error_message(
69
+ db_name=self.db_name,
70
+ db_type=self.db_type,
71
+ db_error_msg=self.db_error_msg,
72
+ failed_query=self.failed_query,
73
+ is_external=self.is_external,
74
+ )
75
+
76
+
38
77
  def format_db_error_message(
39
78
  db_name: str | None = None,
40
79
  db_type: str | None = None,
mindsdb/utilities/fs.py CHANGED
@@ -118,7 +118,6 @@ def clean_unlinked_process_marks() -> List[int]:
118
118
 
119
119
  except psutil.AccessDenied:
120
120
  logger.warning(f"access to {process_id} denied")
121
-
122
121
  continue
123
122
 
124
123
  except psutil.NoSuchProcess:
@@ -46,28 +46,31 @@ def send_profiling_results(profiling_data: dict):
46
46
  user=MINDSDB_PROFILING_DB_USER,
47
47
  password=MINDSDB_PROFILING_DB_PASSWORD,
48
48
  dbname="postgres",
49
- connect_timeout=5
49
+ connect_timeout=5,
50
50
  )
51
51
  except Exception:
52
- logger.error('cant get acceess to profiling database')
52
+ logger.warning("cant get acceess to profiling database")
53
53
  return
54
54
  cur = connection.cursor()
55
- cur.execute("""
55
+ cur.execute(
56
+ """
56
57
  insert into profiling
57
58
  (data, query, time, hostname, environment, api, total_time, company_id, instance_id)
58
59
  values
59
60
  (%s, %s, %s, %s, %s, %s, %s, %s, %s)
60
- """, (
61
- json.dumps(profiling["tree"]),
62
- profiling.get("query", "?"),
63
- time_start_at,
64
- profiling["hostname"],
65
- profiling.get("environment", "?"),
66
- profiling.get("api", "?"),
67
- profiling["tree"]["value"],
68
- profiling["company_id"],
69
- profiling["instance_id"]
70
- ))
61
+ """,
62
+ (
63
+ json.dumps(profiling["tree"]),
64
+ profiling.get("query", "?"),
65
+ time_start_at,
66
+ profiling["hostname"],
67
+ profiling.get("environment", "?"),
68
+ profiling.get("api", "?"),
69
+ profiling["tree"]["value"],
70
+ profiling["company_id"],
71
+ profiling["instance_id"],
72
+ ),
73
+ )
71
74
 
72
75
  connection.commit()
73
76
  cur.close()
@@ -44,17 +44,19 @@ class LangfuseClientWrapper:
44
44
  Langfuse client wrapper. Defines Langfuse client configuration and initializes Langfuse client.
45
45
  """
46
46
 
47
- def __init__(self,
48
- public_key: str = LANGFUSE_PUBLIC_KEY,
49
- secret_key: str = LANGFUSE_SECRET_KEY,
50
- host: str = LANGFUSE_HOST,
51
- environment: str = LANGFUSE_ENVIRONMENT,
52
- release: str = LANGFUSE_RELEASE,
53
- debug: bool = LANGFUSE_DEBUG,
54
- timeout: int = LANGFUSE_TIMEOUT,
55
- sample_rate: float = LANGFUSE_SAMPLE_RATE,
56
- disable: bool = LANGFUSE_DISABLED,
57
- force_run: bool = LANGFUSE_FORCE_RUN) -> None:
47
+ def __init__(
48
+ self,
49
+ public_key: str = LANGFUSE_PUBLIC_KEY,
50
+ secret_key: str = LANGFUSE_SECRET_KEY,
51
+ host: str = LANGFUSE_HOST,
52
+ environment: str = LANGFUSE_ENVIRONMENT,
53
+ release: str = LANGFUSE_RELEASE,
54
+ debug: bool = LANGFUSE_DEBUG,
55
+ timeout: int = LANGFUSE_TIMEOUT,
56
+ sample_rate: float = LANGFUSE_SAMPLE_RATE,
57
+ disable: bool = LANGFUSE_DISABLED,
58
+ force_run: bool = LANGFUSE_FORCE_RUN,
59
+ ) -> None:
58
60
  """
59
61
  Initialize Langfuse client.
60
62
 
@@ -112,16 +114,18 @@ class LangfuseClientWrapper:
112
114
  release=release,
113
115
  debug=debug,
114
116
  timeout=timeout,
115
- sample_rate=sample_rate
117
+ sample_rate=sample_rate,
116
118
  )
117
119
 
118
- def setup_trace(self,
119
- name: str,
120
- input: typing.Optional[typing.Any] = None,
121
- tags: typing.Optional[typing.List] = None,
122
- metadata: typing.Optional[typing.Dict] = None,
123
- user_id: str = None,
124
- session_id: str = None) -> None:
120
+ def setup_trace(
121
+ self,
122
+ name: str,
123
+ input: typing.Optional[typing.Any] = None,
124
+ tags: typing.Optional[typing.List] = None,
125
+ metadata: typing.Optional[typing.Dict] = None,
126
+ user_id: str = None,
127
+ session_id: str = None,
128
+ ) -> None:
125
129
  """
126
130
  Setup trace. If Langfuse is disabled, nothing will be done.
127
131
  Args:
@@ -142,15 +146,10 @@ class LangfuseClientWrapper:
142
146
 
143
147
  try:
144
148
  self.trace = self.client.trace(
145
- name=name,
146
- input=input,
147
- metadata=self.metadata,
148
- tags=self.tags,
149
- user_id=user_id,
150
- session_id=session_id
149
+ name=name, input=input, metadata=self.metadata, tags=self.tags, user_id=user_id, session_id=session_id
151
150
  )
152
- except Exception as e:
153
- logger.error(f'Something went wrong while processing Langfuse trace {self.trace.id}: {str(e)}')
151
+ except Exception:
152
+ logger.exception(f"Something went wrong while processing Langfuse trace {self.trace.id}:")
154
153
 
155
154
  logger.info(f"Langfuse trace configured with ID: {self.trace.id}")
156
155
 
@@ -169,9 +168,7 @@ class LangfuseClientWrapper:
169
168
 
170
169
  return self.trace.id
171
170
 
172
- def start_span(self,
173
- name: str,
174
- input: typing.Optional[typing.Any] = None) -> typing.Optional['StatefulSpanClient']:
171
+ def start_span(self, name: str, input: typing.Optional[typing.Any] = None) -> typing.Optional["StatefulSpanClient"]:
175
172
  """
176
173
  Create span. If Langfuse is disabled, nothing will be done.
177
174
 
@@ -186,8 +183,7 @@ class LangfuseClientWrapper:
186
183
 
187
184
  return self.trace.span(name=name, input=input)
188
185
 
189
- def end_span_stream(self,
190
- span: typing.Optional['StatefulSpanClient'] = None) -> None:
186
+ def end_span_stream(self, span: typing.Optional["StatefulSpanClient"] = None) -> None:
191
187
  """
192
188
  End span. If Langfuse is disabled, nothing will happen.
193
189
  Args:
@@ -201,9 +197,9 @@ class LangfuseClientWrapper:
201
197
  span.end()
202
198
  self.trace.update()
203
199
 
204
- def end_span(self,
205
- span: typing.Optional['StatefulSpanClient'] = None,
206
- output: typing.Optional[typing.Any] = None) -> None:
200
+ def end_span(
201
+ self, span: typing.Optional["StatefulSpanClient"] = None, output: typing.Optional[typing.Any] = None
202
+ ) -> None:
207
203
  """
208
204
  End trace. If Langfuse is disabled, nothing will be done.
209
205
 
@@ -228,13 +224,12 @@ class LangfuseClientWrapper:
228
224
  try:
229
225
  # Ensure all batched traces are sent before fetching.
230
226
  self.client.flush()
231
- metadata['tool_usage'] = self._get_tool_usage()
227
+ metadata["tool_usage"] = self._get_tool_usage()
232
228
  self.trace.update(metadata=metadata)
229
+ except Exception:
230
+ logger.exception(f"Something went wrong while processing Langfuse trace {self.trace.id}:")
233
231
 
234
- except Exception as e:
235
- logger.error(f'Something went wrong while processing Langfuse trace {self.trace.id}: {str(e)}')
236
-
237
- def get_langchain_handler(self) -> typing.Optional['CallbackHandler']:
232
+ def get_langchain_handler(self) -> typing.Optional["CallbackHandler"]:
238
233
  """
239
234
  Get Langchain handler. If Langfuse is disabled, returns None.
240
235
  """
@@ -275,14 +270,14 @@ class LangfuseClientWrapper:
275
270
  fetched_trace = self.client.get_trace(self.trace.id)
276
271
  steps = [s.name for s in fetched_trace.observations]
277
272
  for step in steps:
278
- if 'AgentAction' in step:
279
- tool_name = step.split('-')[1]
273
+ if "AgentAction" in step:
274
+ tool_name = step.split("-")[1]
280
275
  if tool_name not in tool_usage:
281
276
  tool_usage[tool_name] = 0
282
277
  tool_usage[tool_name] += 1
283
278
  except TraceNotFoundError:
284
- logger.warning(f'Langfuse trace {self.trace.id} not found')
285
- except Exception as e:
286
- logger.error(f'Something went wrong while processing Langfuse trace {self.trace.id}: {str(e)}')
279
+ logger.warning(f"Langfuse trace {self.trace.id} not found")
280
+ except Exception:
281
+ logger.exception(f"Something went wrong while processing Langfuse trace {self.trace.id}:")
287
282
 
288
283
  return tool_usage