flock-core 0.5.0b10__py3-none-any.whl → 0.5.0b12__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 flock-core might be problematic. Click here for more details.
- flock/components/evaluation/declarative_evaluation_component.py +10 -2
- flock/components/utility/output_utility_component.py +2 -1
- flock/core/component/evaluation_component.py +6 -0
- flock/core/flock_agent.py +1 -1
- flock/core/flock_factory.py +4 -0
- flock/core/orchestration/flock_execution.py +1 -1
- flock/core/orchestration/flock_initialization.py +1 -1
- flock/core/util/cli_helper.py +4 -1
- flock/webapp/app/api/execution.py +248 -58
- flock/webapp/app/chat.py +63 -2
- flock/webapp/app/services/feedback_file_service.py +363 -0
- flock/webapp/app/services/sharing_store.py +167 -5
- flock/webapp/templates/partials/_execution_form.html +61 -21
- flock/webapp/templates/partials/_registry_viewer_content.html +30 -6
- flock/webapp/templates/registry_viewer.html +21 -18
- {flock_core-0.5.0b10.dist-info → flock_core-0.5.0b12.dist-info}/METADATA +4 -1
- {flock_core-0.5.0b10.dist-info → flock_core-0.5.0b12.dist-info}/RECORD +20 -19
- {flock_core-0.5.0b10.dist-info → flock_core-0.5.0b12.dist-info}/WHEEL +0 -0
- {flock_core-0.5.0b10.dist-info → flock_core-0.5.0b12.dist-info}/entry_points.txt +0 -0
- {flock_core-0.5.0b10.dist-info → flock_core-0.5.0b12.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""DeclarativeEvaluationComponent - DSPy-based evaluation using the unified component system."""
|
|
3
3
|
|
|
4
4
|
from collections.abc import Generator
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, override
|
|
6
6
|
|
|
7
7
|
from temporalio import workflow
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ class DeclarativeEvaluationConfig(AgentComponentConfig):
|
|
|
29
29
|
model: str | None = "openai/gpt-4o"
|
|
30
30
|
use_cache: bool = True
|
|
31
31
|
temperature: float = 0.0
|
|
32
|
-
max_tokens: int =
|
|
32
|
+
max_tokens: int = 8192
|
|
33
33
|
max_retries: int = 3
|
|
34
34
|
max_tool_calls: int = 10
|
|
35
35
|
stream: bool = Field(
|
|
@@ -69,6 +69,14 @@ class DeclarativeEvaluationComponent(
|
|
|
69
69
|
def __init__(self, **data):
|
|
70
70
|
super().__init__(**data)
|
|
71
71
|
|
|
72
|
+
@override
|
|
73
|
+
def set_model(self, model: str, temperature: float = 0.0, max_tokens: int = 8192) -> None:
|
|
74
|
+
"""Set the model for the evaluation component."""
|
|
75
|
+
self.config.model = model
|
|
76
|
+
if "gpt-oss" in model and temperature == 0.0 and max_tokens == 8192:
|
|
77
|
+
self.config.temperature = 1.0
|
|
78
|
+
self.config.max_tokens = 32768
|
|
79
|
+
|
|
72
80
|
async def evaluate_core(
|
|
73
81
|
self,
|
|
74
82
|
agent: Any,
|
|
@@ -180,7 +180,8 @@ class OutputUtilityComponent(UtilityComponent):
|
|
|
180
180
|
max_length=self.config.max_length,
|
|
181
181
|
render_table=self.config.render_table,
|
|
182
182
|
)
|
|
183
|
-
|
|
183
|
+
model = agent.model if agent.model else context.get_variable("model")
|
|
184
|
+
self._formatter.display_result(result_to_display, agent.name + " - " + model)
|
|
184
185
|
|
|
185
186
|
return result # Return the original, unmodified result
|
|
186
187
|
|
|
@@ -24,6 +24,12 @@ class EvaluationComponent(AgentComponent):
|
|
|
24
24
|
- LLMEvaluationComponent (direct LLM API)
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def set_model(self, model: str, temperature: float = 0.0, max_tokens: int = 4096) -> None:
|
|
29
|
+
"""Set the model for the evaluation component."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
27
33
|
@abstractmethod
|
|
28
34
|
async def evaluate_core(
|
|
29
35
|
self,
|
flock/core/flock_agent.py
CHANGED
|
@@ -258,7 +258,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
|
|
|
258
258
|
"""
|
|
259
259
|
self.model = model
|
|
260
260
|
if self.evaluator and hasattr(self.evaluator, "config"):
|
|
261
|
-
self.evaluator.
|
|
261
|
+
self.evaluator.set_model(model)
|
|
262
262
|
logger.info(
|
|
263
263
|
f"Set model to '{model}' for agent '{self.name}' and its evaluator."
|
|
264
264
|
)
|
flock/core/flock_factory.py
CHANGED
|
@@ -133,7 +133,7 @@ class FlockExecution:
|
|
|
133
133
|
span.set_attribute("enable_temporal", self.flock.enable_temporal)
|
|
134
134
|
|
|
135
135
|
logger.info(
|
|
136
|
-
f"Initiating Flock run '{self.flock.name}'. Start Agent: '{start_agent_name}'. Temporal: {self.flock.enable_temporal}."
|
|
136
|
+
f"Initiating Flock run '{self.flock.name}'. Model: '{self.flock.model}'. Start Agent: '{start_agent_name}'. Temporal: {self.flock.enable_temporal}."
|
|
137
137
|
)
|
|
138
138
|
|
|
139
139
|
try:
|
|
@@ -41,7 +41,7 @@ class FlockInitialization:
|
|
|
41
41
|
|
|
42
42
|
# Initialize console if needed for banner
|
|
43
43
|
if self.flock.show_flock_banner:
|
|
44
|
-
init_console(clear_screen=True, show_banner=self.flock.show_flock_banner)
|
|
44
|
+
init_console(clear_screen=True, show_banner=self.flock.show_flock_banner, model=self.flock.model)
|
|
45
45
|
|
|
46
46
|
# Set Temporal debug environment variable
|
|
47
47
|
self._set_temporal_debug_flag()
|
flock/core/util/cli_helper.py
CHANGED
|
@@ -38,7 +38,7 @@ def display_hummingbird():
|
|
|
38
38
|
""")
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def init_console(clear_screen: bool = True, show_banner: bool = True):
|
|
41
|
+
def init_console(clear_screen: bool = True, show_banner: bool = True, model: str = ""):
|
|
42
42
|
"""Display the Flock banner."""
|
|
43
43
|
from rich.console import Console
|
|
44
44
|
from rich.syntax import Text
|
|
@@ -66,6 +66,9 @@ def init_console(clear_screen: bool = True, show_banner: bool = True):
|
|
|
66
66
|
"[italic]'Magpie'[/] milestone - [bold]white duck GmbH[/] - [cyan]https://whiteduck.de[/]\n"
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
if model:
|
|
70
|
+
console.print(f"[italic]Global Model:[/] {model}")
|
|
71
|
+
|
|
69
72
|
|
|
70
73
|
def display_banner_no_version():
|
|
71
74
|
"""Display the Flock banner."""
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# src/flock/webapp/app/api/execution.py
|
|
2
|
+
import html
|
|
2
3
|
import json
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
5
6
|
|
|
6
7
|
import markdown2 # Import markdown2
|
|
7
8
|
from fastapi import ( # Ensure Form and HTTPException are imported
|
|
@@ -11,8 +12,16 @@ from fastapi import ( # Ensure Form and HTTPException are imported
|
|
|
11
12
|
Request,
|
|
12
13
|
)
|
|
13
14
|
from fastapi.encoders import jsonable_encoder
|
|
14
|
-
from fastapi.responses import HTMLResponse
|
|
15
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
|
15
16
|
from fastapi.templating import Jinja2Templates
|
|
17
|
+
from werkzeug.utils import secure_filename
|
|
18
|
+
|
|
19
|
+
from flock.webapp.app.services.feedback_file_service import (
|
|
20
|
+
create_csv_feedback_file,
|
|
21
|
+
create_csv_feedback_file_for_agent,
|
|
22
|
+
create_xlsx_feedback_file,
|
|
23
|
+
create_xlsx_feedback_file_for_agent,
|
|
24
|
+
)
|
|
16
25
|
|
|
17
26
|
if TYPE_CHECKING:
|
|
18
27
|
from flock.core.flock import Flock
|
|
@@ -41,26 +50,30 @@ router = APIRouter()
|
|
|
41
50
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
42
51
|
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
43
52
|
|
|
53
|
+
|
|
44
54
|
# Add markdown2 filter to Jinja2 environment for this router
|
|
45
55
|
def markdown_filter(text):
|
|
46
56
|
return markdown2.markdown(text, extras=["tables", "fenced-code-blocks"])
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
|
|
59
|
+
templates.env.filters["markdown"] = markdown_filter
|
|
49
60
|
|
|
50
61
|
|
|
51
62
|
@router.get("/htmx/execution-form-content", response_class=HTMLResponse)
|
|
52
63
|
async def htmx_get_execution_form_content(
|
|
53
64
|
request: Request,
|
|
54
|
-
current_flock: "Flock | None" = Depends(
|
|
65
|
+
current_flock: "Flock | None" = Depends(
|
|
66
|
+
get_optional_flock_instance
|
|
67
|
+
), # Use optional if form can show 'no flock'
|
|
55
68
|
):
|
|
56
69
|
# flock instance is injected
|
|
57
70
|
return templates.TemplateResponse(
|
|
58
71
|
"partials/_execution_form.html",
|
|
59
72
|
{
|
|
60
73
|
"request": request,
|
|
61
|
-
"flock": current_flock,
|
|
74
|
+
"flock": current_flock, # Pass the injected flock instance
|
|
62
75
|
"input_fields": [],
|
|
63
|
-
"selected_agent_name": None,
|
|
76
|
+
"selected_agent_name": None, # Form starts with no agent selected
|
|
64
77
|
},
|
|
65
78
|
)
|
|
66
79
|
|
|
@@ -69,7 +82,9 @@ async def htmx_get_execution_form_content(
|
|
|
69
82
|
async def htmx_get_agent_input_form(
|
|
70
83
|
request: Request,
|
|
71
84
|
agent_name: str,
|
|
72
|
-
current_flock: "Flock" = Depends(
|
|
85
|
+
current_flock: "Flock" = Depends(
|
|
86
|
+
get_flock_instance
|
|
87
|
+
), # Expect flock to be loaded
|
|
73
88
|
):
|
|
74
89
|
# flock instance is injected
|
|
75
90
|
agent = current_flock.agents.get(agent_name)
|
|
@@ -88,12 +103,21 @@ async def htmx_get_agent_input_form(
|
|
|
88
103
|
"type": type_str.lower(),
|
|
89
104
|
"description": description or "",
|
|
90
105
|
}
|
|
91
|
-
if "bool" in field_info["type"]:
|
|
92
|
-
|
|
93
|
-
elif
|
|
106
|
+
if "bool" in field_info["type"]:
|
|
107
|
+
field_info["html_type"] = "checkbox"
|
|
108
|
+
elif (
|
|
109
|
+
"int" in field_info["type"] or "float" in field_info["type"]
|
|
110
|
+
):
|
|
111
|
+
field_info["html_type"] = "number"
|
|
112
|
+
elif (
|
|
113
|
+
"list" in field_info["type"] or "dict" in field_info["type"]
|
|
114
|
+
):
|
|
94
115
|
field_info["html_type"] = "textarea"
|
|
95
|
-
field_info["placeholder"] =
|
|
96
|
-
|
|
116
|
+
field_info["placeholder"] = (
|
|
117
|
+
f"Enter JSON for {field_info['type']}"
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
field_info["html_type"] = "text"
|
|
97
121
|
input_fields.append(field_info)
|
|
98
122
|
except Exception as e:
|
|
99
123
|
return HTMLResponse(
|
|
@@ -109,7 +133,9 @@ async def htmx_get_agent_input_form(
|
|
|
109
133
|
async def htmx_run_flock(
|
|
110
134
|
request: Request,
|
|
111
135
|
):
|
|
112
|
-
current_flock_from_state: Flock | None = getattr(
|
|
136
|
+
current_flock_from_state: Flock | None = getattr(
|
|
137
|
+
request.app.state, "flock_instance", None
|
|
138
|
+
)
|
|
113
139
|
logger = get_flock_logger("webapp.execution.regular_run")
|
|
114
140
|
|
|
115
141
|
if not current_flock_from_state:
|
|
@@ -125,7 +151,9 @@ async def htmx_run_flock(
|
|
|
125
151
|
|
|
126
152
|
agent = current_flock_from_state.agents.get(start_agent_name)
|
|
127
153
|
if not agent:
|
|
128
|
-
logger.error(
|
|
154
|
+
logger.error(
|
|
155
|
+
f"HTMX Run (Regular): Agent '{start_agent_name}' not found in Flock '{current_flock_from_state.name}'."
|
|
156
|
+
)
|
|
129
157
|
return HTMLResponse(
|
|
130
158
|
f"<p class='error'>Agent '{start_agent_name}' not found in the current Flock.</p>"
|
|
131
159
|
)
|
|
@@ -137,30 +165,57 @@ async def htmx_run_flock(
|
|
|
137
165
|
for name, type_str, _ in parsed_spec:
|
|
138
166
|
form_field_name = f"agent_input_{name}"
|
|
139
167
|
raw_value = form_data.get(form_field_name)
|
|
140
|
-
if raw_value is None and "bool" in type_str.lower():
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
if raw_value is None and "bool" in type_str.lower():
|
|
169
|
+
inputs[name] = False
|
|
170
|
+
continue
|
|
171
|
+
if raw_value is None:
|
|
172
|
+
inputs[name] = None
|
|
173
|
+
continue
|
|
174
|
+
if "int" in type_str.lower():
|
|
175
|
+
inputs[name] = int(raw_value)
|
|
176
|
+
elif "float" in type_str.lower():
|
|
177
|
+
inputs[name] = float(raw_value)
|
|
178
|
+
elif "bool" in type_str.lower():
|
|
179
|
+
inputs[name] = raw_value.lower() in [
|
|
180
|
+
"true",
|
|
181
|
+
"on",
|
|
182
|
+
"1",
|
|
183
|
+
"yes",
|
|
184
|
+
]
|
|
185
|
+
elif "list" in type_str.lower() or "dict" in type_str.lower():
|
|
186
|
+
inputs[name] = json.loads(raw_value)
|
|
187
|
+
else:
|
|
188
|
+
inputs[name] = raw_value
|
|
147
189
|
except ValueError as ve:
|
|
148
|
-
logger.error(
|
|
149
|
-
|
|
190
|
+
logger.error(
|
|
191
|
+
f"HTMX Run (Regular): Input parsing error for agent '{start_agent_name}': {ve}",
|
|
192
|
+
exc_info=True,
|
|
193
|
+
)
|
|
194
|
+
return HTMLResponse(
|
|
195
|
+
"<p class='error'>Invalid input format. Please check your input and try again.</p>"
|
|
196
|
+
)
|
|
150
197
|
except Exception as e_parse:
|
|
151
|
-
logger.error(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
logger.error(
|
|
199
|
+
f"HTMX Run (Regular): Error processing inputs for '{start_agent_name}': {e_parse}",
|
|
200
|
+
exc_info=True,
|
|
201
|
+
)
|
|
202
|
+
return HTMLResponse(
|
|
203
|
+
f"<p class='error'>Error processing inputs for {html.escape(str(start_agent_name))}: {html.escape(str(e_parse))}</p>"
|
|
204
|
+
)
|
|
155
205
|
|
|
206
|
+
result_data = await run_current_flock_service(
|
|
207
|
+
start_agent_name, inputs, request.app.state
|
|
208
|
+
)
|
|
156
209
|
|
|
157
210
|
raw_json_for_template = json.dumps(
|
|
158
|
-
jsonable_encoder(
|
|
211
|
+
jsonable_encoder(
|
|
212
|
+
result_data
|
|
213
|
+
), # ← converts every nested BaseModel, datetime, etc.
|
|
159
214
|
indent=2,
|
|
160
|
-
ensure_ascii=False
|
|
215
|
+
ensure_ascii=False,
|
|
161
216
|
)
|
|
162
217
|
# Unescape newlines for proper display in HTML <pre> tag
|
|
163
|
-
result_data_raw_json_str = raw_json_for_template.replace(
|
|
218
|
+
result_data_raw_json_str = raw_json_for_template.replace("\\n", "\n")
|
|
164
219
|
root_path = request.scope.get("root_path", "")
|
|
165
220
|
return templates.TemplateResponse(
|
|
166
221
|
"partials/_results_display.html",
|
|
@@ -173,7 +228,7 @@ async def htmx_run_flock(
|
|
|
173
228
|
"flock_name": current_flock_from_state.name,
|
|
174
229
|
"agent_name": start_agent_name,
|
|
175
230
|
"flock_definition": current_flock_from_state.to_yaml(),
|
|
176
|
-
}
|
|
231
|
+
},
|
|
177
232
|
)
|
|
178
233
|
|
|
179
234
|
|
|
@@ -189,54 +244,98 @@ async def htmx_run_shared_flock(
|
|
|
189
244
|
|
|
190
245
|
if not start_agent_name:
|
|
191
246
|
shared_logger.warning("HTMX Run Shared: Starting agent not selected.")
|
|
192
|
-
return HTMLResponse(
|
|
247
|
+
return HTMLResponse(
|
|
248
|
+
"<p class='error'>Starting agent not selected for shared run.</p>"
|
|
249
|
+
)
|
|
193
250
|
|
|
194
251
|
inputs: dict[str, Any] = {}
|
|
195
252
|
try:
|
|
196
|
-
shared_flocks_store = getattr(request.app.state,
|
|
253
|
+
shared_flocks_store = getattr(request.app.state, "shared_flocks", {})
|
|
197
254
|
temp_flock = shared_flocks_store.get(share_id)
|
|
198
255
|
|
|
199
256
|
if not temp_flock:
|
|
200
|
-
shared_logger.error(
|
|
201
|
-
|
|
257
|
+
shared_logger.error(
|
|
258
|
+
f"HTMX Run Shared: Flock instance for share_id '{share_id}' not found in app.state."
|
|
259
|
+
)
|
|
260
|
+
return HTMLResponse(
|
|
261
|
+
f"<p class='error'>Shared session not found or expired. Please try accessing the shared link again.</p>"
|
|
262
|
+
)
|
|
202
263
|
|
|
203
|
-
shared_logger.info(
|
|
264
|
+
shared_logger.info(
|
|
265
|
+
f"HTMX Run Shared: Successfully retrieved pre-loaded Flock '{temp_flock.name}' for agent '{start_agent_name}' (share_id: {share_id})."
|
|
266
|
+
)
|
|
204
267
|
|
|
205
268
|
agent = temp_flock.agents.get(start_agent_name)
|
|
206
269
|
if not agent:
|
|
207
|
-
shared_logger.error(
|
|
208
|
-
|
|
270
|
+
shared_logger.error(
|
|
271
|
+
f"HTMX Run Shared: Agent '{start_agent_name}' not found in shared Flock '{temp_flock.name}'."
|
|
272
|
+
)
|
|
273
|
+
return HTMLResponse(
|
|
274
|
+
f"<p class='error'>Agent '{start_agent_name}' not found in the provided shared Flock definition.</p>"
|
|
275
|
+
)
|
|
209
276
|
|
|
210
277
|
if agent.input and isinstance(agent.input, str):
|
|
211
278
|
parsed_spec = parse_schema(agent.input)
|
|
212
279
|
for name, type_str, _ in parsed_spec:
|
|
213
280
|
form_field_name = f"agent_input_{name}"
|
|
214
281
|
raw_value = form_data.get(form_field_name)
|
|
215
|
-
if raw_value is None and "bool" in type_str.lower():
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
if raw_value is None and "bool" in type_str.lower():
|
|
283
|
+
inputs[name] = False
|
|
284
|
+
continue
|
|
285
|
+
if raw_value is None:
|
|
286
|
+
inputs[name] = None
|
|
287
|
+
continue
|
|
288
|
+
if "int" in type_str.lower():
|
|
289
|
+
inputs[name] = int(raw_value)
|
|
290
|
+
elif "float" in type_str.lower():
|
|
291
|
+
inputs[name] = float(raw_value)
|
|
292
|
+
elif "bool" in type_str.lower():
|
|
293
|
+
inputs[name] = raw_value.lower() in [
|
|
294
|
+
"true",
|
|
295
|
+
"on",
|
|
296
|
+
"1",
|
|
297
|
+
"yes",
|
|
298
|
+
]
|
|
299
|
+
elif "list" in type_str.lower() or "dict" in type_str.lower():
|
|
300
|
+
inputs[name] = json.loads(raw_value)
|
|
301
|
+
else:
|
|
302
|
+
inputs[name] = raw_value
|
|
303
|
+
|
|
304
|
+
shared_logger.info(
|
|
305
|
+
f"HTMX Run Shared: Executing agent '{start_agent_name}' in pre-loaded Flock '{temp_flock.name}'. Inputs: {list(inputs.keys())}"
|
|
306
|
+
)
|
|
307
|
+
result_data = await temp_flock.run_async(
|
|
308
|
+
start_agent=start_agent_name, input=inputs, box_result=False
|
|
309
|
+
)
|
|
225
310
|
raw_json_for_template = json.dumps(
|
|
226
|
-
jsonable_encoder(
|
|
311
|
+
jsonable_encoder(
|
|
312
|
+
result_data
|
|
313
|
+
), # ← converts every nested BaseModel, datetime, etc.
|
|
227
314
|
indent=2,
|
|
228
|
-
ensure_ascii=False
|
|
315
|
+
ensure_ascii=False,
|
|
229
316
|
)
|
|
230
317
|
# Unescape newlines for proper display in HTML <pre> tag
|
|
231
|
-
result_data_raw_json_str = raw_json_for_template.replace(
|
|
232
|
-
shared_logger.info(
|
|
318
|
+
result_data_raw_json_str = raw_json_for_template.replace("\\n", "\n")
|
|
319
|
+
shared_logger.info(
|
|
320
|
+
f"HTMX Run Shared: Agent '{start_agent_name}' executed. Result keys: {list(result_data.keys()) if isinstance(result_data, dict) else 'N/A'}"
|
|
321
|
+
)
|
|
233
322
|
|
|
234
323
|
except ValueError as ve:
|
|
235
|
-
shared_logger.error(
|
|
236
|
-
|
|
324
|
+
shared_logger.error(
|
|
325
|
+
f"HTMX Run Shared: Input parsing error for '{start_agent_name}' (share_id: {share_id}): {ve}",
|
|
326
|
+
exc_info=True,
|
|
327
|
+
)
|
|
328
|
+
return HTMLResponse(
|
|
329
|
+
f"<p class='error'>Invalid input format: {ve!s}</p>"
|
|
330
|
+
)
|
|
237
331
|
except Exception as e:
|
|
238
|
-
shared_logger.error(
|
|
239
|
-
|
|
332
|
+
shared_logger.error(
|
|
333
|
+
f"HTMX Run Shared: Error during execution for '{start_agent_name}' (share_id: {share_id}): {e}",
|
|
334
|
+
exc_info=True,
|
|
335
|
+
)
|
|
336
|
+
return HTMLResponse(
|
|
337
|
+
f"<p class='error'>An unexpected error occurred: {e!s}</p>"
|
|
338
|
+
)
|
|
240
339
|
root_path = request.scope.get("root_path", "")
|
|
241
340
|
|
|
242
341
|
return templates.TemplateResponse(
|
|
@@ -250,9 +349,10 @@ async def htmx_run_shared_flock(
|
|
|
250
349
|
"flock_name": temp_flock.name,
|
|
251
350
|
"agent_name": start_agent_name,
|
|
252
351
|
"flock_definition": temp_flock.to_yaml(),
|
|
253
|
-
}
|
|
352
|
+
},
|
|
254
353
|
)
|
|
255
354
|
|
|
355
|
+
|
|
256
356
|
# --- Feedback endpoints ---
|
|
257
357
|
@router.post("/htmx/feedback", response_class=HTMLResponse)
|
|
258
358
|
async def htmx_submit_feedback(
|
|
@@ -292,6 +392,8 @@ async def htmx_submit_feedback_shared(
|
|
|
292
392
|
expected_response: str | None = Form(None),
|
|
293
393
|
actual_response: str | None = Form(None),
|
|
294
394
|
flock_definition: str | None = Form(None),
|
|
395
|
+
flock_name: str | None = Form(None),
|
|
396
|
+
agent_name: str | None = Form(None),
|
|
295
397
|
store: SharedLinkStoreInterface = Depends(get_shared_link_store),
|
|
296
398
|
):
|
|
297
399
|
from uuid import uuid4
|
|
@@ -306,6 +408,94 @@ async def htmx_submit_feedback_shared(
|
|
|
306
408
|
expected_response=expected_response,
|
|
307
409
|
actual_response=actual_response,
|
|
308
410
|
flock_definition=flock_definition,
|
|
411
|
+
agent_name=agent_name,
|
|
412
|
+
flock_name=flock_name,
|
|
309
413
|
)
|
|
310
414
|
await store.save_feedback(record)
|
|
311
|
-
return HTMLResponse(
|
|
415
|
+
return HTMLResponse(
|
|
416
|
+
"<p>🙏 Feedback received for shared run – thank you!</p>"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@router.get("/htmx/feedback-download/{format}", response_class=FileResponse)
|
|
421
|
+
async def chat_feedback_download_all(
|
|
422
|
+
request: Request,
|
|
423
|
+
format: Literal["csv", "xlsx"] = "csv",
|
|
424
|
+
store: SharedLinkStoreInterface = Depends(get_shared_link_store),
|
|
425
|
+
):
|
|
426
|
+
"""Download all feedback records for all agents in the current flock as a CSV file.
|
|
427
|
+
|
|
428
|
+
This function iterates through all agents in the currently loaded flock and collects
|
|
429
|
+
all feedback records for each agent, then exports them as a single CSV file.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
request: The FastAPI request object
|
|
433
|
+
store: The shared link store interface dependency
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
FileResponse: CSV/XLSX file containing all feedback records for all agents
|
|
437
|
+
|
|
438
|
+
Raises:
|
|
439
|
+
HTTPException: If no flock is loaded or no agents are found in the flock
|
|
440
|
+
"""
|
|
441
|
+
safe_format = secure_filename(format)
|
|
442
|
+
if safe_format == "csv":
|
|
443
|
+
return await create_csv_feedback_file(
|
|
444
|
+
request=request,
|
|
445
|
+
store=store,
|
|
446
|
+
separator=","
|
|
447
|
+
)
|
|
448
|
+
elif safe_format == "xlsx":
|
|
449
|
+
return await create_xlsx_feedback_file(
|
|
450
|
+
request=request,
|
|
451
|
+
store=store,
|
|
452
|
+
)
|
|
453
|
+
else:
|
|
454
|
+
from fastapi import HTTPException
|
|
455
|
+
|
|
456
|
+
raise HTTPException(
|
|
457
|
+
status_code=400,
|
|
458
|
+
detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@router.get("/htmx/feedback-download/{agent_name}/{format}", response_class=FileResponse)
|
|
463
|
+
async def chat_feedback_download(
|
|
464
|
+
request: Request,
|
|
465
|
+
agent_name: str,
|
|
466
|
+
format: Literal["csv", "xlsx"] = "csv",
|
|
467
|
+
store: SharedLinkStoreInterface = Depends(get_shared_link_store),
|
|
468
|
+
):
|
|
469
|
+
"""Download all feedback records for a specific agent as a file.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
request: The FastAPI request object
|
|
473
|
+
agent_name: Name of the agent to download feedback for
|
|
474
|
+
store: The shared link store interface dependency
|
|
475
|
+
format: Either 'csv' or 'xlsx' the file format to use
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
FileResponse: CSV/XLSX file containing all feedback records for the specified agent
|
|
479
|
+
"""
|
|
480
|
+
safe_format = secure_filename(format)
|
|
481
|
+
safe_agent_name = secure_filename(agent_name)
|
|
482
|
+
if safe_format == "csv":
|
|
483
|
+
return await create_csv_feedback_file_for_agent(
|
|
484
|
+
request=request,
|
|
485
|
+
store=store,
|
|
486
|
+
separator=",",
|
|
487
|
+
agent_name=safe_agent_name,
|
|
488
|
+
)
|
|
489
|
+
elif safe_format == "xlsx":
|
|
490
|
+
return await create_xlsx_feedback_file_for_agent(
|
|
491
|
+
request=request,
|
|
492
|
+
store=store,
|
|
493
|
+
agent_name=safe_agent_name,
|
|
494
|
+
)
|
|
495
|
+
else:
|
|
496
|
+
from fastapi import HTTPException
|
|
497
|
+
|
|
498
|
+
raise HTTPException(
|
|
499
|
+
status_code=400,
|
|
500
|
+
detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
|
|
501
|
+
)
|
flock/webapp/app/chat.py
CHANGED
|
@@ -3,17 +3,24 @@ from __future__ import annotations
|
|
|
3
3
|
import ast # Add import ast
|
|
4
4
|
import json
|
|
5
5
|
from datetime import datetime
|
|
6
|
+
from typing import Literal
|
|
6
7
|
from uuid import uuid4
|
|
7
8
|
|
|
8
9
|
import markdown2 # Added for Markdown to HTML conversion
|
|
9
10
|
from fastapi import APIRouter, Depends, Form, Request, Response
|
|
10
|
-
from fastapi.responses import HTMLResponse
|
|
11
|
+
from fastapi.responses import FileResponse, HTMLResponse
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
|
|
13
14
|
from flock.core.flock import Flock
|
|
14
15
|
from flock.core.logging.logging import get_logger
|
|
15
16
|
from flock.webapp.app.dependencies import get_shared_link_store
|
|
16
17
|
from flock.webapp.app.main import get_base_context_web, templates
|
|
18
|
+
from flock.webapp.app.services.feedback_file_service import (
|
|
19
|
+
create_csv_feedback_file,
|
|
20
|
+
create_csv_feedback_file_for_agent,
|
|
21
|
+
create_xlsx_feedback_file,
|
|
22
|
+
create_xlsx_feedback_file_for_agent,
|
|
23
|
+
)
|
|
17
24
|
from flock.webapp.app.services.sharing_models import (
|
|
18
25
|
FeedbackRecord,
|
|
19
26
|
SharedLinkConfig,
|
|
@@ -572,7 +579,6 @@ async def chat_feedback(request: Request,
|
|
|
572
579
|
headers = {"HX-Trigger": json.dumps(toast_event)}
|
|
573
580
|
return Response(status_code=204, headers=headers)
|
|
574
581
|
|
|
575
|
-
|
|
576
582
|
@router.post("/chat/htmx/feedback-shared", response_class=HTMLResponse, include_in_schema=False)
|
|
577
583
|
async def chat_feedback_shared(request: Request,
|
|
578
584
|
share_id: str = Form(...),
|
|
@@ -602,3 +608,58 @@ async def chat_feedback_shared(request: Request,
|
|
|
602
608
|
}
|
|
603
609
|
headers = {"HX-Trigger": json.dumps(toast_event)}
|
|
604
610
|
return Response(status_code=204, headers=headers)
|
|
611
|
+
|
|
612
|
+
@router.get("/chat/htmx/feedback-download/{format}", response_class=FileResponse, include_in_schema=False)
|
|
613
|
+
async def chat_feedback_download_all(
|
|
614
|
+
request: Request,
|
|
615
|
+
format: Literal["csv", "xlsx"] = "csv",
|
|
616
|
+
store: SharedLinkStoreInterface = Depends(get_shared_link_store)
|
|
617
|
+
):
|
|
618
|
+
"""Download all feedback records for all agents."""
|
|
619
|
+
if format == "csv":
|
|
620
|
+
return await create_csv_feedback_file(
|
|
621
|
+
request=request,
|
|
622
|
+
store=store,
|
|
623
|
+
separator=","
|
|
624
|
+
)
|
|
625
|
+
elif format == "xlsx":
|
|
626
|
+
return await create_xlsx_feedback_file(
|
|
627
|
+
request=request,
|
|
628
|
+
store=store,
|
|
629
|
+
)
|
|
630
|
+
else:
|
|
631
|
+
from fastapi import HTTPException
|
|
632
|
+
|
|
633
|
+
raise HTTPException(
|
|
634
|
+
status_code=400,
|
|
635
|
+
detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
@router.get("/chat/htmx/feedback-download/{agent_name}/{format}", response_class=FileResponse, include_in_schema=False)
|
|
639
|
+
async def chat_feedback_download(
|
|
640
|
+
request: Request,
|
|
641
|
+
agent_name: str,
|
|
642
|
+
format: Literal["csv", "xlsx"] = "csv",
|
|
643
|
+
store: SharedLinkStoreInterface = Depends(get_shared_link_store)
|
|
644
|
+
):
|
|
645
|
+
"""Download all feedback records for a specific agent as a File."""
|
|
646
|
+
if format == "csv":
|
|
647
|
+
return await create_csv_feedback_file_for_agent(
|
|
648
|
+
request=request,
|
|
649
|
+
store=store,
|
|
650
|
+
separator=",",
|
|
651
|
+
agent_name=agent_name,
|
|
652
|
+
)
|
|
653
|
+
elif format == "xlsx":
|
|
654
|
+
return await create_xlsx_feedback_file_for_agent(
|
|
655
|
+
request=request,
|
|
656
|
+
store=store,
|
|
657
|
+
agent_name=agent_name,
|
|
658
|
+
)
|
|
659
|
+
else:
|
|
660
|
+
from fastapi import HTTPException
|
|
661
|
+
|
|
662
|
+
raise HTTPException(
|
|
663
|
+
status_code=400,
|
|
664
|
+
detail="Invalid file-format specified. Valid formats are: 'csv', 'xlsx'"
|
|
665
|
+
)
|