fairo 25.9.1__tar.gz → 25.9.3__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.
- {fairo-25.9.1 → fairo-25.9.3}/PKG-INFO +1 -1
- fairo-25.9.3/fairo/__init__.py +1 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/chat/chat.py +2 -2
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/execution/executor.py +4 -1
- fairo-25.9.3/fairo/core/tools/__init__.py +2 -0
- fairo-25.9.3/fairo/core/tools/plot.py +229 -0
- fairo-25.9.3/fairo/core/tools/suggestion.py +43 -0
- fairo-25.9.3/fairo/core/utils.py +54 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/workflow/dependency.py +0 -3
- {fairo-25.9.1 → fairo-25.9.3}/fairo.egg-info/PKG-INFO +1 -1
- {fairo-25.9.1 → fairo-25.9.3}/fairo.egg-info/SOURCES.txt +4 -0
- fairo-25.9.1/fairo/__init__.py +0 -1
- {fairo-25.9.1 → fairo-25.9.3}/README.md +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/base_agent.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/code_analysis_agent.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/output/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/output/base_output.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/output/google_drive.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/tools/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/tools/base_tools.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/tools/code_analysis.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/tools/utils.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/agent/utils.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/chat/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/client/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/client/client.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/exceptions.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/execution/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/execution/agent_serializer.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/execution/env_finder.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/execution/model_log_helper.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/models/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/models/custom_field_value.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/models/resources.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/runnable/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/runnable/runnable.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/workflow/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/workflow/base_workflow.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/core/workflow/utils.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/metrics/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/metrics/fairness_object.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/metrics/metrics.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/settings.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/tests/__init__.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo/tests/test_metrics.py +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo.egg-info/dependency_links.txt +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo.egg-info/requires.txt +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/fairo.egg-info/top_level.txt +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/pyproject.toml +0 -0
- {fairo-25.9.1 → fairo-25.9.3}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "25.9.3"
|
|
@@ -193,11 +193,11 @@ class ChatFairo(ChatMlflow):
|
|
|
193
193
|
|
|
194
194
|
@property
|
|
195
195
|
def _target_uri(self):
|
|
196
|
-
return os.environ.get("MLFLOW_GATEWAY_URI",
|
|
196
|
+
return os.environ.get("MLFLOW_GATEWAY_URI", get_mlflow_gateway_uri())
|
|
197
197
|
|
|
198
198
|
@property
|
|
199
199
|
def _endpoint(self):
|
|
200
|
-
return os.environ.get("MLFLOW_GATEWAY_ROUTE",
|
|
200
|
+
return os.environ.get("MLFLOW_GATEWAY_ROUTE", get_mlflow_gateway_chat_route())
|
|
201
201
|
|
|
202
202
|
def invoke(self, *args, **kwargs):
|
|
203
203
|
# Override invoke to use dynamic target_uri
|
|
@@ -18,7 +18,7 @@ from fairo.core.execution.model_log_helper import ModelLogHelper
|
|
|
18
18
|
from fairo.core.runnable.runnable import Runnable
|
|
19
19
|
from fairo.core.workflow.utils import output_langchain_process_graph
|
|
20
20
|
from fairo.settings import get_fairo_api_key, get_fairo_api_secret, get_mlflow_experiment_name, get_mlflow_server, get_fairo_base_url
|
|
21
|
-
|
|
21
|
+
from fairo.core.tools import ChatSuggestions
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
@@ -35,6 +35,7 @@ class FairoExecutor:
|
|
|
35
35
|
chain_class = SimpleSequentialChain,
|
|
36
36
|
input_fields: List[str] = [],
|
|
37
37
|
input_schema: Optional[Type[BaseModel]] = None,
|
|
38
|
+
chat_suggestions: Optional[ChatSuggestions] = None
|
|
38
39
|
):
|
|
39
40
|
if agents and runnable:
|
|
40
41
|
raise ValueError("FairoExecutor cannot be initialized with both 'agents' and 'runnable'. Please provide only one.")
|
|
@@ -55,6 +56,7 @@ class FairoExecutor:
|
|
|
55
56
|
password=get_fairo_api_secret(),
|
|
56
57
|
username=get_fairo_api_key()
|
|
57
58
|
)
|
|
59
|
+
self.chat_suggestions = chat_suggestions
|
|
58
60
|
self.input_fields = input_fields
|
|
59
61
|
# Inject shared attributes into agents
|
|
60
62
|
for agent in self.agents:
|
|
@@ -121,6 +123,7 @@ class FairoExecutor:
|
|
|
121
123
|
"process_graph": process_graph,
|
|
122
124
|
"schema": self.input_schema.model_json_schema() if self.input_schema else None,
|
|
123
125
|
"input_fields": list(self.input_schema.model_fields.keys()) if self.input_schema else self.input_fields,
|
|
126
|
+
"chat_suggestions": self.chat_suggestions.model_dump() if self.chat_suggestions else None,
|
|
124
127
|
}
|
|
125
128
|
if process_graph:
|
|
126
129
|
mlflow.log_text(json.dumps(fairo_settings, ensure_ascii=False, indent=2), artifact_file="fairo_settings.txt")
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from typing import Dict, List, Literal, Optional, Union
|
|
2
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from langchain_core.tools import tool
|
|
5
|
+
import matplotlib
|
|
6
|
+
matplotlib.use("Agg") # headless
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
import io
|
|
9
|
+
import base64
|
|
10
|
+
|
|
11
|
+
class ChartTypeEnum(str, Enum):
|
|
12
|
+
line = "line"
|
|
13
|
+
bar = "bar"
|
|
14
|
+
scatter = "scatter"
|
|
15
|
+
hist = "hist"
|
|
16
|
+
area = "area"
|
|
17
|
+
|
|
18
|
+
EXAMPLES = [
|
|
19
|
+
{
|
|
20
|
+
"chart_type": "line",
|
|
21
|
+
"data": {"month": ["Jan","Feb","Mar"], "revenue": [10, 12, 15]},
|
|
22
|
+
"x": "month",
|
|
23
|
+
"y": "revenue",
|
|
24
|
+
"title": "Revenue by Month"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"chart_type": "bar",
|
|
28
|
+
"data": {"cat": ["A","B"], "m1": [1,2], "m2":[2,1]},
|
|
29
|
+
"x": "cat",
|
|
30
|
+
"y": ["m1","m2"],
|
|
31
|
+
"title": "Grouped Bars"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
class PlotSpec(BaseModel):
|
|
36
|
+
"""
|
|
37
|
+
Either provide a 'spec' for a chart OR provide 'python_code' to execute.
|
|
38
|
+
|
|
39
|
+
If you provide 'spec', set:
|
|
40
|
+
- chart_type: one of line|bar|scatter|hist|area
|
|
41
|
+
- data: list of dicts (table rows) OR {"x":[...], "y":[...]} arrays
|
|
42
|
+
- x: x key (for list-of-dicts) or leave None if arrays
|
|
43
|
+
- y: y key or list of keys for multi-series
|
|
44
|
+
- title, xlabel, ylabel: optional labels
|
|
45
|
+
- width, height in inches (optional)
|
|
46
|
+
If you provide 'python_code', it must create a 'fig' and 'ax' variable.
|
|
47
|
+
"""
|
|
48
|
+
model_config = ConfigDict(extra="allow", json_schema_extra={"examples": EXAMPLES})
|
|
49
|
+
|
|
50
|
+
# Option A: high-level specification
|
|
51
|
+
chart_type: Optional[ChartTypeEnum] = Field(None, description="Chart type.")
|
|
52
|
+
data: Optional[Union[List[Dict], Dict[str, List]]] = Field(
|
|
53
|
+
None, description="Either list of dict rows OR dict of arrays."
|
|
54
|
+
)
|
|
55
|
+
x: Optional[Union[str, List[str]]] = Field(None, description="X column or key(s).")
|
|
56
|
+
y: Optional[Union[str, List[str]]] = Field(None, description="Y column or key(s).")
|
|
57
|
+
title: Optional[str] = None
|
|
58
|
+
xlabel: Optional[str] = None
|
|
59
|
+
ylabel: Optional[str] = None
|
|
60
|
+
width: Optional[float] = Field(6.0, description="Figure width in inches.")
|
|
61
|
+
height: Optional[float] = Field(4.0, description="Figure height in inches.")
|
|
62
|
+
legend: Optional[bool] = True
|
|
63
|
+
grid: Optional[bool] = True
|
|
64
|
+
alpha: Optional[float] = 1.0
|
|
65
|
+
|
|
66
|
+
# Option B: low-level custom code (SAFE-ish)
|
|
67
|
+
python_code: Optional[str] = Field(
|
|
68
|
+
None,
|
|
69
|
+
description=(
|
|
70
|
+
"Matplotlib code that defines 'fig, ax'. Avoid imports; "
|
|
71
|
+
"numpy is available as 'np' and matplotlib.pyplot as 'plt'."
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _png_base64_from_fig(fig) -> str:
|
|
77
|
+
buf = io.BytesIO()
|
|
78
|
+
fig.savefig(buf, format="png", dpi=150, bbox_inches="tight")
|
|
79
|
+
buf.seek(0)
|
|
80
|
+
b64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
81
|
+
buf.close()
|
|
82
|
+
return b64
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _plot_from_spec(spec: PlotSpec) -> str:
|
|
86
|
+
# Build figure
|
|
87
|
+
fig, ax = plt.subplots(figsize=(spec.width or 6.0, spec.height or 4.0))
|
|
88
|
+
|
|
89
|
+
# Normalize data
|
|
90
|
+
data = spec.data or {}
|
|
91
|
+
is_rows = isinstance(data, list)
|
|
92
|
+
is_arrays = isinstance(data, dict) and all(isinstance(v, list) for v in data.values())
|
|
93
|
+
|
|
94
|
+
def extract_series(xkey, ykey):
|
|
95
|
+
if is_rows:
|
|
96
|
+
xs = [row.get(xkey) for row in data]
|
|
97
|
+
ys = [row.get(ykey) for row in data]
|
|
98
|
+
elif is_arrays:
|
|
99
|
+
xs = data.get(xkey) if xkey else range(len(data.get(ykey, [])))
|
|
100
|
+
ys = data.get(ykey, [])
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError("Unsupported data format. Provide list[dict] or dict of arrays.")
|
|
103
|
+
return xs, ys
|
|
104
|
+
|
|
105
|
+
# Handle y being single or multiple
|
|
106
|
+
y_keys = [spec.y] if isinstance(spec.y, (str, type(None))) else list(spec.y or [])
|
|
107
|
+
if not y_keys: # auto-choose 'y' if present
|
|
108
|
+
y_keys = ["y"] if (is_arrays and "y" in data) else []
|
|
109
|
+
if not y_keys:
|
|
110
|
+
raise ValueError("Could not infer 'y' series; please set y.")
|
|
111
|
+
|
|
112
|
+
# Plot by chart_type
|
|
113
|
+
if spec.chart_type == "line":
|
|
114
|
+
for ykey in y_keys:
|
|
115
|
+
xs, ys = extract_series(spec.x, ykey)
|
|
116
|
+
ax.plot(xs, ys, alpha=spec.alpha, label=ykey if len(y_keys) > 1 else None)
|
|
117
|
+
elif spec.chart_type == "bar":
|
|
118
|
+
# Simple grouped bar if multiple y
|
|
119
|
+
import numpy as np
|
|
120
|
+
xs, _ = extract_series(spec.x, y_keys[0])
|
|
121
|
+
x_index = np.arange(len(xs))
|
|
122
|
+
n = len(y_keys)
|
|
123
|
+
width = 0.8 / n
|
|
124
|
+
for i, ykey in enumerate(y_keys):
|
|
125
|
+
_, ys = extract_series(spec.x, ykey)
|
|
126
|
+
ax.bar(x_index + i * width, ys, width=width, alpha=spec.alpha, label=ykey)
|
|
127
|
+
ax.set_xticks(x_index + width * (n - 1) / 2)
|
|
128
|
+
ax.set_xticklabels(xs, rotation=0)
|
|
129
|
+
elif spec.chart_type == "scatter":
|
|
130
|
+
for ykey in y_keys:
|
|
131
|
+
xs, ys = extract_series(spec.x, ykey)
|
|
132
|
+
ax.scatter(xs, ys, alpha=spec.alpha, label=ykey if len(y_keys) > 1 else None)
|
|
133
|
+
elif spec.chart_type == "hist":
|
|
134
|
+
# Expect a single series in y
|
|
135
|
+
xs, ys = extract_series(spec.x, y_keys[0])
|
|
136
|
+
values = ys if ys else xs
|
|
137
|
+
ax.hist(values, alpha=spec.alpha)
|
|
138
|
+
elif spec.chart_type == "area":
|
|
139
|
+
for ykey in y_keys:
|
|
140
|
+
xs, ys = extract_series(spec.x, ykey)
|
|
141
|
+
ax.fill_between(xs, ys, alpha=spec.alpha, label=ykey if len(y_keys) > 1 else None)
|
|
142
|
+
else:
|
|
143
|
+
raise ValueError("Unsupported chart_type. Use line|bar|scatter|hist|area.")
|
|
144
|
+
|
|
145
|
+
# Labels & cosmetics
|
|
146
|
+
if spec.title:
|
|
147
|
+
ax.set_title(spec.title)
|
|
148
|
+
if spec.xlabel:
|
|
149
|
+
ax.set_xlabel(spec.xlabel)
|
|
150
|
+
if spec.ylabel:
|
|
151
|
+
ax.set_ylabel(spec.ylabel)
|
|
152
|
+
if spec.grid:
|
|
153
|
+
ax.grid(True, linestyle="--", linewidth=0.5, alpha=0.5)
|
|
154
|
+
if spec.legend and len(y_keys) > 1:
|
|
155
|
+
ax.legend()
|
|
156
|
+
|
|
157
|
+
# Return base64
|
|
158
|
+
b64 = _png_base64_from_fig(fig)
|
|
159
|
+
plt.close(fig)
|
|
160
|
+
return b64
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _plot_from_code(code: str) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Execute minimal matplotlib code that must define 'fig' and 'ax'.
|
|
166
|
+
VERY LIMITED namespace to reduce risk.
|
|
167
|
+
"""
|
|
168
|
+
import numpy as np
|
|
169
|
+
|
|
170
|
+
allowed_globals = {
|
|
171
|
+
"__builtins__": {
|
|
172
|
+
"len": len, "range": range, "min": min, "max": max, "sum": sum, "abs": abs
|
|
173
|
+
},
|
|
174
|
+
"np": np,
|
|
175
|
+
"plt": plt,
|
|
176
|
+
}
|
|
177
|
+
local_vars = {}
|
|
178
|
+
|
|
179
|
+
# Simple guardrails
|
|
180
|
+
forbidden = ["import os", "import sys", "open(", "subprocess", "socket", "eval(", "exec("]
|
|
181
|
+
if any(tok in code for tok in forbidden):
|
|
182
|
+
raise ValueError("Disallowed token in python_code.")
|
|
183
|
+
|
|
184
|
+
exec(code, allowed_globals, local_vars) # noqa: S102 (intentional, guarded)
|
|
185
|
+
fig = local_vars.get("fig")
|
|
186
|
+
ax = local_vars.get("ax")
|
|
187
|
+
if fig is None or ax is None:
|
|
188
|
+
raise ValueError("Your python_code must create variables 'fig' and 'ax'.")
|
|
189
|
+
b64 = _png_base64_from_fig(fig)
|
|
190
|
+
plt.close(fig)
|
|
191
|
+
return b64
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class PlotReturn(BaseModel):
|
|
195
|
+
mime_type: Literal["image/png"] = "image/png"
|
|
196
|
+
data_base64: str
|
|
197
|
+
alt_text: Optional[str] = None
|
|
198
|
+
debug: Optional[str] = None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@tool(args_schema=PlotSpec)
|
|
202
|
+
def generate_plot(**kwargs) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Generate a plot image (PNG base64). Returns a JSON string with keys:
|
|
205
|
+
- mime_type: 'image/png'
|
|
206
|
+
- data_base64: <base64 string>
|
|
207
|
+
- alt_text: optional
|
|
208
|
+
"""
|
|
209
|
+
spec = PlotSpec(**kwargs)
|
|
210
|
+
try:
|
|
211
|
+
if spec.python_code:
|
|
212
|
+
b64 = _plot_from_code(spec.python_code)
|
|
213
|
+
alt = spec.title or "Custom matplotlib figure"
|
|
214
|
+
else:
|
|
215
|
+
b64 = _plot_from_spec(spec)
|
|
216
|
+
alt = spec.title or (
|
|
217
|
+
f"{spec.chart_type} plot" if spec.chart_type else "Plot"
|
|
218
|
+
)
|
|
219
|
+
result = PlotReturn(data_base64=b64, alt_text=alt)
|
|
220
|
+
# Return a stringified JSON so the LLM can pass it through easily
|
|
221
|
+
return f"""data:{result.mime_type};base64,{result.data_base64}"""
|
|
222
|
+
except Exception as e:
|
|
223
|
+
# Return an error payload the orchestrator can handle
|
|
224
|
+
err = PlotReturn(
|
|
225
|
+
data_base64="",
|
|
226
|
+
alt_text="Plot generation failed.",
|
|
227
|
+
debug=str(e),
|
|
228
|
+
)
|
|
229
|
+
return err.model_dump_json()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List, Literal, Optional
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from langchain_core.tools import tool
|
|
5
|
+
|
|
6
|
+
class ChatSuggestion(BaseModel):
|
|
7
|
+
action: Optional[Literal["enable_chat"]] = Field(default=None, description="Custom actions available to be dispatched by the chat interface")
|
|
8
|
+
prompt: str = Field(..., description="Suggestion text to show in UI")
|
|
9
|
+
model_config = {
|
|
10
|
+
"extra": "ignore"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class ChatSuggestions(BaseModel):
|
|
14
|
+
chat_enabled: Optional[bool] = Field(default=True, description="This will let user only answer using the suggestions provided")
|
|
15
|
+
suggestions: List[ChatSuggestion] = Field(..., description="List of suggestions that will be available for the user")
|
|
16
|
+
|
|
17
|
+
@tool(args_schema=ChatSuggestions)
|
|
18
|
+
def send_chat_suggestions(chat_enabled: Optional[bool] = True,
|
|
19
|
+
suggestions: List[ChatSuggestion] = None):
|
|
20
|
+
"""
|
|
21
|
+
This tool can be used to provide user predefined prompts and help during the user experience
|
|
22
|
+
Example input:
|
|
23
|
+
{
|
|
24
|
+
"suggestions": [
|
|
25
|
+
{
|
|
26
|
+
"action": null,
|
|
27
|
+
"prompt": "Suggestion 1 Prompt"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"action": null,
|
|
31
|
+
"prompt": "Suggestion 2 Prompt"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"action": "enable_chat",
|
|
35
|
+
"prompt": "Other"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
# No side-effects needed; return the same payload so it's accessible
|
|
41
|
+
if not suggestions:
|
|
42
|
+
return []
|
|
43
|
+
return {"chat_enabled": chat_enabled, "suggestions": [s.model_dump() for s in suggestions]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from langchain_core.messages import ToolMessage
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def parse_chat_interface_output(agent_executor_result):
|
|
6
|
+
"""
|
|
7
|
+
Parses agent executor result into chat interface response
|
|
8
|
+
return_intermediate_steps must be set as true on the AgentExecutor in order to properly parse plot and suggestions
|
|
9
|
+
"""
|
|
10
|
+
messages = [{"role": "assistant", "content": [
|
|
11
|
+
{
|
|
12
|
+
"type": "text",
|
|
13
|
+
"text": agent_executor_result["output"]
|
|
14
|
+
}
|
|
15
|
+
]}]
|
|
16
|
+
suggestions = []
|
|
17
|
+
intermediate_steps = agent_executor_result.get('intermediate_steps', [])
|
|
18
|
+
for step, output in intermediate_steps:
|
|
19
|
+
if step.tool == "generate_plot":
|
|
20
|
+
messages.append({"role": "assistant", "content": [
|
|
21
|
+
{
|
|
22
|
+
"type": "image",
|
|
23
|
+
"image": output
|
|
24
|
+
}
|
|
25
|
+
]})
|
|
26
|
+
if step.tool == "send_chat_suggestions":
|
|
27
|
+
suggestions = output
|
|
28
|
+
|
|
29
|
+
# Check if some tool message has artifact and raw_html attribute
|
|
30
|
+
artifact = None
|
|
31
|
+
is_tool_msg = isinstance(output, ToolMessage)
|
|
32
|
+
if is_tool_msg:
|
|
33
|
+
artifact = getattr(output, "artifact", None)
|
|
34
|
+
if artifact is None:
|
|
35
|
+
artifact = getattr(output, "additional_kwargs", {}).get("artifact")
|
|
36
|
+
if artifact:
|
|
37
|
+
artifact_id = artifact.get("artifact_id")
|
|
38
|
+
if artifact_id:
|
|
39
|
+
base_dir = Path("/tmp") if Path("/tmp").exists() else Path.cwd()
|
|
40
|
+
artifact_path = base_dir / f"{artifact_id}.html"
|
|
41
|
+
messages.append({
|
|
42
|
+
"role": "assistant",
|
|
43
|
+
"content": [{
|
|
44
|
+
"type": "file",
|
|
45
|
+
"mimeType": "text/html",
|
|
46
|
+
"data": artifact_path.read_text(encoding="utf-8")
|
|
47
|
+
}]
|
|
48
|
+
})
|
|
49
|
+
if os.path.exists(artifact_path):
|
|
50
|
+
os.remove(artifact_path)
|
|
51
|
+
return {
|
|
52
|
+
"messages": messages,
|
|
53
|
+
"suggestions": suggestions
|
|
54
|
+
}
|
|
@@ -122,9 +122,6 @@ class FairoVectorStore(BaseVectorStore):
|
|
|
122
122
|
# Convert documents to Fairo format
|
|
123
123
|
docs_data = []
|
|
124
124
|
for doc in documents:
|
|
125
|
-
# Generate embeddings for the document content
|
|
126
|
-
embedding = self.embeddings.embed_query(doc.page_content)
|
|
127
|
-
|
|
128
125
|
# Create doc entry (let Fairo API generate document IDs)
|
|
129
126
|
doc_entry = {
|
|
130
127
|
"page_content": doc.page_content,
|
|
@@ -9,6 +9,7 @@ fairo.egg-info/requires.txt
|
|
|
9
9
|
fairo.egg-info/top_level.txt
|
|
10
10
|
fairo/core/__init__.py
|
|
11
11
|
fairo/core/exceptions.py
|
|
12
|
+
fairo/core/utils.py
|
|
12
13
|
fairo/core/agent/__init__.py
|
|
13
14
|
fairo/core/agent/base_agent.py
|
|
14
15
|
fairo/core/agent/code_analysis_agent.py
|
|
@@ -34,6 +35,9 @@ fairo/core/models/custom_field_value.py
|
|
|
34
35
|
fairo/core/models/resources.py
|
|
35
36
|
fairo/core/runnable/__init__.py
|
|
36
37
|
fairo/core/runnable/runnable.py
|
|
38
|
+
fairo/core/tools/__init__.py
|
|
39
|
+
fairo/core/tools/plot.py
|
|
40
|
+
fairo/core/tools/suggestion.py
|
|
37
41
|
fairo/core/workflow/__init__.py
|
|
38
42
|
fairo/core/workflow/base_workflow.py
|
|
39
43
|
fairo/core/workflow/dependency.py
|
fairo-25.9.1/fairo/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "25.9.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|