fairo 25.5.4__tar.gz → 25.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (45) hide show
  1. {fairo-25.5.4 → fairo-25.6.1}/PKG-INFO +1 -1
  2. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/base_agent.py +25 -26
  3. fairo-25.6.1/fairo/core/chat/chat.py +23 -0
  4. fairo-25.6.1/fairo/core/execution/env_finder.py +46 -0
  5. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/execution/executor.py +75 -13
  6. fairo-25.6.1/fairo/core/runnable/runnable.py +77 -0
  7. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/workflow/base_workflow.py +3 -2
  8. fairo-25.6.1/fairo/metrics/__init__.py +0 -0
  9. {fairo-25.5.4 → fairo-25.6.1}/fairo/settings.py +2 -0
  10. fairo-25.6.1/fairo/tests/__init__.py +0 -0
  11. {fairo-25.5.4 → fairo-25.6.1}/fairo.egg-info/PKG-INFO +1 -1
  12. {fairo-25.5.4 → fairo-25.6.1}/fairo.egg-info/SOURCES.txt +5 -0
  13. {fairo-25.5.4 → fairo-25.6.1}/README.md +0 -0
  14. {fairo-25.5.4 → fairo-25.6.1}/fairo/__init__.py +0 -0
  15. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/__init__.py +0 -0
  16. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/__init__.py +0 -0
  17. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/code_analysis_agent.py +0 -0
  18. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/output/__init__.py +0 -0
  19. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/output/base_output.py +0 -0
  20. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/output/google_drive.py +0 -0
  21. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/tools/__init__.py +0 -0
  22. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/tools/base_tools.py +0 -0
  23. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/tools/code_analysis.py +0 -0
  24. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/tools/utils.py +0 -0
  25. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/agent/utils.py +0 -0
  26. {fairo-25.5.4/fairo/core/client → fairo-25.6.1/fairo/core/chat}/__init__.py +0 -0
  27. {fairo-25.5.4/fairo/core/execution → fairo-25.6.1/fairo/core/client}/__init__.py +0 -0
  28. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/client/client.py +0 -0
  29. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/exceptions.py +0 -0
  30. {fairo-25.5.4/fairo/core/models → fairo-25.6.1/fairo/core/execution}/__init__.py +0 -0
  31. {fairo-25.5.4/fairo/core/workflow → fairo-25.6.1/fairo/core/models}/__init__.py +0 -0
  32. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/models/custom_field_value.py +0 -0
  33. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/models/resources.py +0 -0
  34. {fairo-25.5.4/fairo/metrics → fairo-25.6.1/fairo/core/runnable}/__init__.py +0 -0
  35. {fairo-25.5.4/fairo/tests → fairo-25.6.1/fairo/core/workflow}/__init__.py +0 -0
  36. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/workflow/dependency.py +0 -0
  37. {fairo-25.5.4 → fairo-25.6.1}/fairo/core/workflow/utils.py +0 -0
  38. {fairo-25.5.4 → fairo-25.6.1}/fairo/metrics/fairness_object.py +0 -0
  39. {fairo-25.5.4 → fairo-25.6.1}/fairo/metrics/metrics.py +0 -0
  40. {fairo-25.5.4 → fairo-25.6.1}/fairo/tests/test_metrics.py +0 -0
  41. {fairo-25.5.4 → fairo-25.6.1}/fairo.egg-info/dependency_links.txt +0 -0
  42. {fairo-25.5.4 → fairo-25.6.1}/fairo.egg-info/requires.txt +0 -0
  43. {fairo-25.5.4 → fairo-25.6.1}/fairo.egg-info/top_level.txt +0 -0
  44. {fairo-25.5.4 → fairo-25.6.1}/pyproject.toml +0 -0
  45. {fairo-25.5.4 → fairo-25.6.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fairo
3
- Version: 25.5.4
3
+ Version: 25.6.1
4
4
  Summary: SDK for interfacing with Fairo SaaS platform.
5
5
  Author-email: "Fairo Systems, Inc." <support@fairo.ai>
6
6
  License: Apache-2.0
@@ -8,12 +8,12 @@ import sys
8
8
  from typing import Dict, List, Optional, Callable, Any, Tuple
9
9
  from langchain.schema import HumanMessage, AIMessage, SystemMessage
10
10
  from langchain_core.messages import ToolMessage
11
- from langchain_community.chat_models.mlflow import ChatMlflow
12
11
  from langchain_core.runnables.config import RunnableConfig
13
12
  from fairo.core.agent.output.base_output import BaseOutput
14
13
  from fairo.core.agent.tools.base_tools import BaseTool
15
14
  from fairo.core.agent.tools.utils import Iteration, LLMAgentOutput, ToolResult
16
15
  from langchain_core.messages.tool import ToolCall
16
+ from fairo.core.chat.chat import FairoChat
17
17
  from fairo.core.client.client import BaseClient
18
18
  from fairo.core.workflow.dependency import BaseVectorStore
19
19
  from fairo.core.agent.utils import truncate_content, truncate_obj_content
@@ -66,10 +66,9 @@ class SimpleAgent:
66
66
  self.verbose = verbose
67
67
  self.use_langchain_mlflow_chat = True if not llm else False
68
68
  self.workflow_run_id = workflow_run_id
69
- self.llm = llm or ChatMlflow(
70
- target_uri=get_mlflow_gateway_uri(),
69
+ self.llm = llm or FairoChat(
71
70
  endpoint=get_mlflow_gateway_chat_route(),
72
- extra_params={"workflow_run_id": self.workflow_run_id}
71
+ workflow_run_id=self.workflow_run_id
73
72
  )
74
73
  self.memory = memory or []
75
74
  self.conversation_history = []
@@ -782,12 +781,12 @@ class SimpleAgent:
782
781
  output.add_duration(f"{total_execution_time:.2f}s")
783
782
 
784
783
  # Save output
785
- if self.patch_run_output_json:
786
- self.patch_run_output_json(output.to_dict())
784
+ # if self.patch_run_output_json:
785
+ # self.patch_run_output_json(output.to_dict())
787
786
 
788
787
  # Execute output
789
- if self.output and len(self.output) > 0:
790
- self.execute_outputs(final_answer)
788
+ # if self.output and len(self.output) > 0:
789
+ # self.execute_outputs(final_answer)
791
790
 
792
791
  return final_answer
793
792
 
@@ -823,8 +822,8 @@ class SimpleAgent:
823
822
  output.add_duration(f"{total_execution_time:.2f}s")
824
823
 
825
824
  # Make sure to patch the output with the error
826
- if self.patch_run_output_json:
827
- self.patch_run_output_json(output.to_dict())
825
+ # if self.patch_run_output_json:
826
+ # self.patch_run_output_json(output.to_dict())
828
827
 
829
828
  # Re-throw the exception with more context
830
829
  raise Exception(f"Agent execution failed: {error_msg}") from e
@@ -855,22 +854,22 @@ class SimpleAgent:
855
854
  output_errors.append((error_msg, traceback_details))
856
855
 
857
856
  # Report errors after processing all outputs to avoid multiple error records for the same run
858
- if output_errors and self.patch_run_output_json:
859
- # Create a single error output object with all errors
860
- error_output = LLMAgentOutput(
861
- node_name=self.agent_name,
862
- task="Output execution",
863
- context=None,
864
- query_result=None,
865
- memory=None
866
- )
867
-
868
- # Add all collected errors
869
- for error_msg, traceback_details in output_errors:
870
- error_output.add_error(error_msg, traceback_details)
871
-
872
- # Patch once with all errors
873
- self.patch_run_output_json(error_output.to_dict())
857
+ # if output_errors and self.patch_run_output_json:
858
+ # # Create a single error output object with all errors
859
+ # error_output = LLMAgentOutput(
860
+ # node_name=self.agent_name,
861
+ # task="Output execution",
862
+ # context=None,
863
+ # query_result=None,
864
+ # memory=None
865
+ # )
866
+
867
+ # # Add all collected errors
868
+ # for error_msg, traceback_details in output_errors:
869
+ # error_output.add_error(error_msg, traceback_details)
870
+
871
+ # # Patch once with all errors
872
+ # self.patch_run_output_json(error_output.to_dict())
874
873
 
875
874
  def add_memory(self, content: str) -> None:
876
875
  """
@@ -0,0 +1,23 @@
1
+
2
+ from langchain_community.chat_models.mlflow import ChatMlflow
3
+ from mlflow.deployments import get_deploy_client
4
+ import os
5
+
6
+ class FairoChat(ChatMlflow):
7
+ def __init__(self, endpoint, workflow_run_id, **kwargs):
8
+ super().__init__(
9
+ target_uri=os.environ.get('MLFLOW_GATEWAY_URI', None),
10
+ endpoint=endpoint,
11
+ extra_params={"workflow_run_id": workflow_run_id},
12
+ **kwargs
13
+ )
14
+
15
+ @property
16
+ def _target_uri(self):
17
+ return os.environ.get("MLFLOW_GATEWAY_URI", None)
18
+
19
+ def invoke(self, *args, **kwargs):
20
+ # Override invoke to use dynamic target_uri
21
+ self.target_uri = self._target_uri
22
+ self._client = get_deploy_client(self.target_uri)
23
+ return super().invoke(*args, **kwargs)
@@ -0,0 +1,46 @@
1
+ import os
2
+ import re
3
+ import mlflow
4
+
5
+ ENV_PATTERNS = [
6
+ re.compile(r"os\.getenv\(\s*[\"'](\w+)[\"']"), # os.getenv("VAR")
7
+ re.compile(r"os\.environ\.get\(\s*[\"'](\w+)[\"']"), # os.environ.get("VAR")
8
+ re.compile(r"os\.environ\[\s*[\"'](\w+)[\"']\s*\]"), # os.environ["VAR"]
9
+ ]
10
+
11
+ def find_env_vars_in_file(file_path):
12
+ found_vars = set()
13
+ try:
14
+ with open(file_path, "r", encoding="utf-8") as f:
15
+ content = f.read()
16
+ for pattern in ENV_PATTERNS:
17
+ found_vars.update(pattern.findall(content))
18
+ except (UnicodeDecodeError, OSError):
19
+ pass # Skip unreadable files
20
+ return found_vars
21
+
22
+ def recursively_find_env_vars(start_path, exclude_dirs=None):
23
+ if exclude_dirs is None:
24
+ exclude_dirs = {"venv", ".venv", "__pycache__", ".git", ".mypy_cache", ".pytest_cache", "site-packages"}
25
+
26
+ all_vars = set()
27
+ for root, dirs, files in os.walk(start_path):
28
+ # Skip excluded directories
29
+ dirs[:] = [d for d in dirs if d not in exclude_dirs and not d.startswith(".")]
30
+
31
+ for file in files:
32
+ if file.endswith(".py"):
33
+ full_path = os.path.join(root, file)
34
+ file_vars = find_env_vars_in_file(full_path)
35
+ if file_vars:
36
+ print(f"Found in {full_path}: {file_vars}")
37
+ all_vars.update(file_vars)
38
+ return all_vars
39
+
40
+ def read_variables() -> str:
41
+ current_path = os.getcwd()
42
+ env_vars = recursively_find_env_vars(current_path)
43
+ logged_vars = ""
44
+ for var in env_vars:
45
+ logged_vars += f"{var}\n"
46
+ return logged_vars
@@ -1,42 +1,51 @@
1
+ import json
2
+ import os
1
3
  import types
2
4
  from typing import List, Any, Callable, Dict, Union
3
5
  from langchain_core.runnables import RunnableLambda, RunnableSequence
4
6
  import logging
5
7
 
8
+ import mlflow
9
+
10
+ from fairo.core.client.client import BaseClient
11
+ from fairo.core.execution.env_finder import read_variables
12
+ from fairo.core.runnable.runnable import Runnable
13
+ from fairo.settings import get_fairo_api_key, get_fairo_api_secret, get_mlflow_experiment_name, get_mlflow_server, get_fairo_base_url
14
+
6
15
  # Optional interfaces/types
7
16
  class LLMAgentOutput:
8
17
  pass
9
18
 
10
- class BaseClient:
11
- pass
12
-
13
19
  logger = logging.getLogger(__name__)
14
20
 
15
- class AgentExecutor:
21
+ class FairoExecutor:
16
22
  def __init__(
17
23
  self,
18
24
  agents: List[Any],
19
25
  verbose: bool = False,
20
26
  patch_run_output_json: Callable[[LLMAgentOutput], None] = None,
21
- client: BaseClient = None,
22
- workflow_run_id: str = ""
27
+ workflow_run_id: str = "",
28
+ runnable: Runnable = None,
29
+ experiment_name: str = None
23
30
  ):
24
31
  self.agents = agents
25
32
  self.verbose = verbose
26
33
  self.patch_run_output_json = patch_run_output_json
27
- self.client = client
28
34
  self.workflow_run_id = workflow_run_id
29
-
35
+ self.runnable = runnable
36
+ self.experiment_name = experiment_name if experiment_name else get_mlflow_experiment_name()
37
+ self.setup_mlflow()
38
+ self.client = BaseClient(
39
+ base_url=get_fairo_base_url(),
40
+ password=get_fairo_api_secret(),
41
+ username=get_fairo_api_key()
42
+ )
30
43
  # Inject shared attributes into agents
31
44
  for agent in self.agents:
32
- if hasattr(agent, 'patch_run_output_json'):
33
- agent.patch_run_output_json = self.patch_run_output_json
34
45
  if hasattr(agent, 'set_client'):
35
46
  agent.set_client(self.client)
36
47
  if hasattr(agent, 'verbose'):
37
48
  agent.verbose = self.verbose
38
- if hasattr(agent, 'workflow_run_id'):
39
- agent.set_workflow_run_id(self.workflow_run_id)
40
49
 
41
50
  self.pipeline = self._build_pipeline()
42
51
 
@@ -91,7 +100,41 @@ class AgentExecutor:
91
100
  pipeline = runnables[0]
92
101
  for r in runnables[1:]:
93
102
  pipeline = pipeline | r # chaining
103
+
104
+ try:
105
+ # Find environment variables used in the project
106
+ all_env_vars = read_variables()
107
+ # Log the file as an artifact
108
+ mlflow.log_text(all_env_vars, artifact_file="environment/variables.txt")
109
+ if self.verbose:
110
+ logger.info(f"Logged {len(all_env_vars)} environment variables as artifact")
111
+ except Exception as e:
112
+ logger.warning(f"Failed to log environment variables: {str(e)}")
94
113
 
114
+ model_info = mlflow.langchain.log_model(
115
+ pipeline,
116
+ artifact_path="agent",
117
+ )
118
+ # If runnable object was added, check if it exists, if yes, just set tags for the trace, otherwise create it
119
+ if self.runnable:
120
+ if not self.runnable.id:
121
+ result = mlflow.register_model(
122
+ model_uri=model_info.model_uri,
123
+ name=self.runnable.name,
124
+ await_registration_for=0
125
+ )
126
+ self.runnable.create_version(
127
+ artifact_path=result.source,
128
+ registered_model_id=f"models:/{self.runnable.name}/{result.version}"
129
+ )
130
+ mlflow.set_tags({
131
+ "runnable_id": self.runnable.id,
132
+ "environment": "development",
133
+ })
134
+ else:
135
+ mlflow.set_tags({
136
+ "environment": "development",
137
+ })
95
138
  return pipeline
96
139
 
97
140
  def run(self, input_data: Union[str, Dict[str, str]]) -> Dict[str, Any]:
@@ -127,4 +170,23 @@ class AgentExecutor:
127
170
  logger.error(f"Pipeline execution failed: {str(e)}")
128
171
 
129
172
  # Propagate the exception so calling code can handle it
130
- raise
173
+ raise e
174
+
175
+ def setup_mlflow(self):
176
+ def _clean_mlflow_env_vars():
177
+ for env_var in ["MLFLOW_TRACKING_USERNAME", "MLFLOW_TRACKING_PASSWORD", "MLFLOW_TRACKING_TOKEN"]:
178
+ if env_var in os.environ:
179
+ del os.environ[env_var]
180
+ def setup_mlflow_tracking_server():
181
+ os.environ["MLFLOW_TRACKING_USERNAME"] = get_fairo_api_key()
182
+ os.environ["MLFLOW_TRACKING_PASSWORD"] = get_fairo_api_secret()
183
+ mlflow.set_tracking_uri(get_mlflow_server())
184
+ mlflow.set_experiment(experiment_name=self.experiment_name)
185
+ _clean_mlflow_env_vars()
186
+ setup_mlflow_tracking_server()
187
+ with mlflow.start_run():
188
+ mlflow.langchain.autolog(
189
+ log_traces=True,
190
+ log_input_examples=True,
191
+ )
192
+
@@ -0,0 +1,77 @@
1
+ from typing import Any, List, Literal, Optional
2
+
3
+ from fairo.core.agent.base_agent import SimpleAgent
4
+ from fairo.core.client.client import BaseClient
5
+ from fairo.core.workflow.utils import output_workflow_process_graph
6
+ from fairo.settings import get_fairo_base_url, get_fairo_api_key, get_fairo_api_secret
7
+
8
+ class Runnable:
9
+ def __init__(self,
10
+ name: str,
11
+ version: int,
12
+ type: Literal["Agent", "Workflow", "Default"],
13
+ chain: List[Any] = [],
14
+ agent: Optional[SimpleAgent] = None,
15
+ artifact_path: Optional[str] = None,
16
+ description: Optional[str] = ""):
17
+ self.id = None
18
+ self.process_graph = None
19
+ self.name = name
20
+ self.version = version
21
+ self.type = type
22
+ self.chain = chain
23
+ self.agent = agent
24
+ self.artifact_path = artifact_path
25
+ self.description = description
26
+ self.client = BaseClient(
27
+ base_url=get_fairo_base_url(),
28
+ username=get_fairo_api_key(),
29
+ password=get_fairo_api_secret()
30
+ )
31
+
32
+ self.load_version()
33
+
34
+
35
+ def load_version(self):
36
+ try:
37
+ response = self.client.get(f"/runnables?version={self.version}&name={self.name}&page_size=1")
38
+ if len(response.get('results')) > 0:
39
+ runnable_obj = response.get('results')[0]
40
+ self.artifact_path = runnable_obj.get('artifact_path')
41
+ self.type = runnable_obj.get('type')
42
+ self.description = runnable_obj.get('description')
43
+ self.id = runnable_obj.get('id')
44
+ self.process_graph = runnable_obj.get('process_graph')
45
+ except Exception as e:
46
+ print("Failed to check existing runnable version")
47
+ raise e
48
+
49
+ def create_version(self, artifact_path, registered_model_id):
50
+ try:
51
+ process_graph = output_workflow_process_graph(self.chain) if all(isinstance(agent, SimpleAgent) for agent in self.chain) else None
52
+ payload = {
53
+ "name": self.name,
54
+ "version": self.version,
55
+ "artifact_path": artifact_path,
56
+ "description": self.description,
57
+ "type": self.type,
58
+ "registered_model_id": registered_model_id,
59
+ "process_graph": process_graph
60
+ }
61
+ response = self.client.post(f"/runnables", json=payload)
62
+ self.id = response.get('id')
63
+ self.process_graph = response.get('process_graph')
64
+ except Exception as e:
65
+ print("Failed to create runnable version")
66
+ raise e
67
+
68
+ def patch_process_graph(self):
69
+ try:
70
+ process_graph = output_workflow_process_graph(self.chain)
71
+ response = self.client.patch(f"/runnables/{self.id}", json={
72
+ "process_graph": process_graph
73
+ }, auth=(self.api_key, self.api_secret))
74
+ return response.json()
75
+ except Exception as e:
76
+ print(e)
77
+
@@ -8,7 +8,8 @@ from fairo.core.agent.base_agent import SimpleAgent
8
8
  from fairo.core.agent.tools.utils import FlowOutput, LLMAgentOutput
9
9
  from fairo.core.client.client import BaseClient
10
10
 
11
- from fairo.core.execution.executor import AgentExecutor
11
+ from fairo.core.execution.executor import FairoExecutor
12
+ from fairo.core.runnable.runnable import Runnable
12
13
  from fairo.core.workflow.utils import output_workflow_process_graph
13
14
  from fairo.settings import (
14
15
  get_fairo_api_key,
@@ -219,7 +220,7 @@ class BaseWorkflow:
219
220
  Propagates any exceptions from agents.
220
221
  """
221
222
  client = BaseClient(base_url=self.base_url, username=self.api_key, password=self.api_secret)
222
- executor = AgentExecutor(
223
+ executor = FairoExecutor(
223
224
  agents=self.agents,
224
225
  verbose=False,
225
226
  patch_run_output_json=self.add_workflow_run_node_output,
File without changes
@@ -54,6 +54,8 @@ def get_mlflow_token():
54
54
  def get_mlflow_experiment_path():
55
55
  return os.getenv('MLFLOW_EXPERIMENT_PATH', None)
56
56
 
57
+ def get_mlflow_experiment_name():
58
+ return os.getenv('MLFLOW_EXPERIMENT_NAME', "Development Default")
57
59
 
58
60
  def get_use_databricks_tracking_server():
59
61
  return os.getenv('USE_DATABRICKS_TRACKING_SERVER', False)
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fairo
3
- Version: 25.5.4
3
+ Version: 25.6.1
4
4
  Summary: SDK for interfacing with Fairo SaaS platform.
5
5
  Author-email: "Fairo Systems, Inc." <support@fairo.ai>
6
6
  License: Apache-2.0
@@ -20,13 +20,18 @@ fairo/core/agent/tools/__init__.py
20
20
  fairo/core/agent/tools/base_tools.py
21
21
  fairo/core/agent/tools/code_analysis.py
22
22
  fairo/core/agent/tools/utils.py
23
+ fairo/core/chat/__init__.py
24
+ fairo/core/chat/chat.py
23
25
  fairo/core/client/__init__.py
24
26
  fairo/core/client/client.py
25
27
  fairo/core/execution/__init__.py
28
+ fairo/core/execution/env_finder.py
26
29
  fairo/core/execution/executor.py
27
30
  fairo/core/models/__init__.py
28
31
  fairo/core/models/custom_field_value.py
29
32
  fairo/core/models/resources.py
33
+ fairo/core/runnable/__init__.py
34
+ fairo/core/runnable/runnable.py
30
35
  fairo/core/workflow/__init__.py
31
36
  fairo/core/workflow/base_workflow.py
32
37
  fairo/core/workflow/dependency.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes