flock-core 0.4.0b48__py3-none-any.whl → 0.4.0b50__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.

Files changed (32) hide show
  1. flock/__init__.py +45 -3
  2. flock/modules/mem0/mem0_module.py +63 -0
  3. flock/modules/mem0graph/__init__.py +1 -0
  4. flock/modules/mem0graph/mem0_graph_module.py +63 -0
  5. flock/webapp/app/api/execution.py +105 -47
  6. flock/webapp/app/chat.py +315 -24
  7. flock/webapp/app/config.py +15 -1
  8. flock/webapp/app/dependencies.py +22 -0
  9. flock/webapp/app/main.py +414 -14
  10. flock/webapp/app/services/flock_service.py +38 -13
  11. flock/webapp/app/services/sharing_models.py +43 -0
  12. flock/webapp/app/services/sharing_store.py +156 -0
  13. flock/webapp/static/css/chat.css +57 -0
  14. flock/webapp/templates/base.html +91 -1
  15. flock/webapp/templates/chat.html +93 -5
  16. flock/webapp/templates/partials/_agent_detail_form.html +3 -3
  17. flock/webapp/templates/partials/_chat_messages.html +1 -1
  18. flock/webapp/templates/partials/_chat_settings_form.html +22 -0
  19. flock/webapp/templates/partials/_execution_form.html +28 -1
  20. flock/webapp/templates/partials/_flock_properties_form.html +2 -2
  21. flock/webapp/templates/partials/_results_display.html +15 -11
  22. flock/webapp/templates/partials/_share_chat_link_snippet.html +11 -0
  23. flock/webapp/templates/partials/_share_link_snippet.html +35 -0
  24. flock/webapp/templates/partials/_structured_data_view.html +2 -2
  25. flock/webapp/templates/shared_run_page.html +143 -0
  26. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/METADATA +4 -2
  27. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/RECORD +31 -24
  28. flock/modules/zep/zep_module.py +0 -187
  29. /flock/modules/{zep → mem0}/__init__.py +0 -0
  30. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/WHEEL +0 -0
  31. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/entry_points.txt +0 -0
  32. {flock_core-0.4.0b48.dist-info → flock_core-0.4.0b50.dist-info}/licenses/LICENSE +0 -0
flock/__init__.py CHANGED
@@ -16,6 +16,11 @@ def main():
16
16
  action="store_true",
17
17
  help="Start the web interface instead of the CLI",
18
18
  )
19
+ parser.add_argument(
20
+ "--chat",
21
+ action="store_true",
22
+ help="Start a chat interface. If --web is also used, enables chat within the web app; otherwise, starts standalone chat.",
23
+ )
19
24
  parser.add_argument(
20
25
  "--theme",
21
26
  type=str,
@@ -30,7 +35,7 @@ def main():
30
35
  # Set environment variable for theme if provided
31
36
  if args.theme:
32
37
  print(
33
- f"Setting FLOCK_WEB_THEME environment variable to: {args.theme}"
38
+ f"INFO: Setting FLOCK_WEB_THEME environment variable to: {args.theme}"
34
39
  )
35
40
  os.environ["FLOCK_WEB_THEME"] = args.theme
36
41
  else:
@@ -38,10 +43,20 @@ def main():
38
43
  if "FLOCK_WEB_THEME" in os.environ:
39
44
  del os.environ["FLOCK_WEB_THEME"]
40
45
 
46
+ if args.chat: # --web --chat
47
+ print("INFO: Starting web application with chat feature enabled.")
48
+ os.environ["FLOCK_CHAT_ENABLED"] = "true"
49
+ else: # Just --web
50
+ print("INFO: Starting web application.")
51
+ if "FLOCK_CHAT_ENABLED" in os.environ:
52
+ del os.environ["FLOCK_CHAT_ENABLED"]
53
+
54
+ # Ensure standalone chat mode is not active
55
+ if "FLOCK_START_MODE" in os.environ:
56
+ del os.environ["FLOCK_START_MODE"]
57
+
41
58
  # Import and run the standalone webapp main function
42
- # It will now read the theme from the environment variable
43
59
  from flock.webapp.run import main as run_webapp_main
44
-
45
60
  run_webapp_main()
46
61
 
47
62
  except ImportError:
@@ -55,6 +70,33 @@ def main():
55
70
  sys.exit(1)
56
71
  return
57
72
 
73
+ elif args.chat: # Standalone chat mode (args.web is false)
74
+ try:
75
+ print("INFO: Starting standalone chat application.")
76
+ os.environ["FLOCK_START_MODE"] = "chat"
77
+
78
+ # Clear web-specific env vars that might conflict or not apply
79
+ if "FLOCK_WEB_THEME" in os.environ:
80
+ del os.environ["FLOCK_WEB_THEME"]
81
+ if "FLOCK_CHAT_ENABLED" in os.environ:
82
+ del os.environ["FLOCK_CHAT_ENABLED"]
83
+
84
+ # Handle --theme if passed with --chat only
85
+ if args.theme:
86
+ print(f"INFO: Standalone chat mode started with --theme '{args.theme}'. FLOCK_WEB_THEME will be set.")
87
+ os.environ["FLOCK_WEB_THEME"] = args.theme
88
+
89
+ from flock.webapp.run import main as run_webapp_main
90
+ run_webapp_main() # The webapp main needs to interpret FLOCK_START_MODE="chat"
91
+
92
+ except ImportError:
93
+ print("Error: Could not import webapp components for chat. Ensure web dependencies are installed.", file=sys.stderr)
94
+ sys.exit(1)
95
+ except Exception as e:
96
+ print(f"Error starting standalone chat application: {e}", file=sys.stderr)
97
+ sys.exit(1)
98
+ return
99
+
58
100
  # Otherwise, run the CLI interface
59
101
  import questionary
60
102
  from rich.console import Console
@@ -0,0 +1,63 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field
4
+
5
+ from flock.core.context.context import FlockContext
6
+ from flock.core.flock_agent import FlockAgent
7
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
8
+ from flock.core.flock_registry import flock_component
9
+ from flock.core.logging.logging import get_logger
10
+
11
+ logger = get_logger("module.mem0")
12
+
13
+
14
+ class Mem0ModuleConfig(FlockModuleConfig):
15
+ qdrant_host: str = Field(default="localhost", description="Qdrant hostname")
16
+ qdrant_port: int = Field(default=6333, description="Qdrant port")
17
+ collection: str = Field(default="flock_memories", description="Vector collection")
18
+ embedder_provider: str = Field(default="openai", description="'openai' or 'local'")
19
+ embedder_model: str = Field(default="text-embedding-ada-002",
20
+ description="Model name for embeddings")
21
+ # Optional: allow separate LLM for reflection/summarisation
22
+ llm_provider: str | None = Field(default=None)
23
+ llm_model: str | None = Field(default=None)
24
+ top_k: int = Field(default=5, description="Number of memories to retrieve")
25
+
26
+
27
+
28
+ @flock_component(config_class=Mem0ModuleConfig)
29
+ class Mem0Module(FlockModule):
30
+ """Module that adds Zep capabilities to a Flock agent."""
31
+
32
+ name: str = "mem0"
33
+ config: Mem0ModuleConfig = Mem0ModuleConfig()
34
+ session_id: str | None = None
35
+ user_id: str | None = None
36
+
37
+ def __init__(self, name, config: Mem0ModuleConfig) -> None:
38
+ """Initialize Mem0 module."""
39
+ super().__init__(name=name, config=config)
40
+ logger.debug("Initializing Mem0 module")
41
+
42
+
43
+ async def on_post_evaluate(
44
+ self,
45
+ agent: FlockAgent,
46
+ inputs: dict[str, Any],
47
+ context: FlockContext | None = None,
48
+ result: dict[str, Any] | None = None,
49
+ ) -> dict[str, Any]:
50
+
51
+ return result
52
+
53
+ async def on_pre_evaluate(
54
+ self,
55
+ agent: FlockAgent,
56
+ inputs: dict[str, Any],
57
+ context: FlockContext | None = None,
58
+ ) -> dict[str, Any]:
59
+
60
+
61
+
62
+
63
+ return inputs
@@ -0,0 +1 @@
1
+ # Package for modules
@@ -0,0 +1,63 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field
4
+
5
+ from flock.core.context.context import FlockContext
6
+ from flock.core.flock_agent import FlockAgent
7
+ from flock.core.flock_module import FlockModule, FlockModuleConfig
8
+ from flock.core.flock_registry import flock_component
9
+ from flock.core.logging.logging import get_logger
10
+
11
+ logger = get_logger("module.mem0")
12
+
13
+
14
+ class Mem0GraphModuleConfig(FlockModuleConfig):
15
+ qdrant_host: str = Field(default="localhost", description="Qdrant hostname")
16
+ qdrant_port: int = Field(default=6333, description="Qdrant port")
17
+ collection: str = Field(default="flock_memories", description="Vector collection")
18
+ embedder_provider: str = Field(default="openai", description="'openai' or 'local'")
19
+ embedder_model: str = Field(default="text-embedding-ada-002",
20
+ description="Model name for embeddings")
21
+ # Optional: allow separate LLM for reflection/summarisation
22
+ llm_provider: str | None = Field(default=None)
23
+ llm_model: str | None = Field(default=None)
24
+ top_k: int = Field(default=5, description="Number of memories to retrieve")
25
+
26
+
27
+
28
+ @flock_component(config_class=Mem0GraphModuleConfig)
29
+ class Mem0GraphModule(FlockModule):
30
+
31
+
32
+ name: str = "mem0"
33
+ config: Mem0GraphModuleConfig = Mem0GraphModuleConfig()
34
+ session_id: str | None = None
35
+ user_id: str | None = None
36
+
37
+ def __init__(self, name, config: Mem0GraphModuleConfig) -> None:
38
+ """Initialize Mem0 module."""
39
+ super().__init__(name=name, config=config)
40
+ logger.debug("Initializing Mem0 module")
41
+
42
+
43
+ async def on_post_evaluate(
44
+ self,
45
+ agent: FlockAgent,
46
+ inputs: dict[str, Any],
47
+ context: FlockContext | None = None,
48
+ result: dict[str, Any] | None = None,
49
+ ) -> dict[str, Any]:
50
+
51
+ return result
52
+
53
+ async def on_pre_evaluate(
54
+ self,
55
+ agent: FlockAgent,
56
+ inputs: dict[str, Any],
57
+ context: FlockContext | None = None,
58
+ ) -> dict[str, Any]:
59
+
60
+
61
+
62
+
63
+ return inputs
@@ -1,9 +1,15 @@
1
1
  # src/flock/webapp/app/api/execution.py
2
2
  import json
3
3
  from pathlib import Path
4
- from typing import TYPE_CHECKING
5
-
6
- from fastapi import APIRouter, Depends, Request # Added Depends
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import markdown2 # Import markdown2
7
+ from fastapi import ( # Ensure Form and HTTPException are imported
8
+ APIRouter,
9
+ Depends,
10
+ Form,
11
+ Request,
12
+ )
7
13
  from fastapi.responses import HTMLResponse
8
14
  from fastapi.templating import Jinja2Templates
9
15
 
@@ -11,6 +17,9 @@ if TYPE_CHECKING:
11
17
  from flock.core.flock import Flock
12
18
 
13
19
 
20
+ from flock.core.logging.logging import (
21
+ get_logger as get_flock_logger, # For logging within the new endpoint
22
+ )
14
23
  from flock.core.util.spliter import parse_schema
15
24
 
16
25
  # Import the dependency to get the current Flock instance
@@ -24,12 +33,17 @@ from flock.webapp.app.services.flock_service import (
24
33
  run_current_flock_service,
25
34
  # get_current_flock_instance IS NO LONGER IMPORTED
26
35
  )
27
- from flock.webapp.app.utils import pydantic_to_dict
28
36
 
29
37
  router = APIRouter()
30
38
  BASE_DIR = Path(__file__).resolve().parent.parent.parent
31
39
  templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
32
40
 
41
+ # Add markdown2 filter to Jinja2 environment for this router
42
+ def markdown_filter(text):
43
+ return markdown2.markdown(text, extras=["tables", "fenced-code-blocks"])
44
+
45
+ templates.env.filters['markdown'] = markdown_filter
46
+
33
47
 
34
48
  @router.get("/htmx/execution-form-content", response_class=HTMLResponse)
35
49
  async def htmx_get_execution_form_content(
@@ -91,25 +105,24 @@ async def htmx_get_agent_input_form(
91
105
  @router.post("/htmx/run", response_class=HTMLResponse)
92
106
  async def htmx_run_flock(
93
107
  request: Request,
94
- # current_flock: Flock = Depends(get_flock_instance) # Service will use app_state
95
108
  ):
96
- # The service function run_current_flock_service now takes app_state
97
- # We retrieve current_flock from app_state inside the service or before calling if needed for validation here
109
+ current_flock_from_state: Flock | None = getattr(request.app.state, 'flock_instance', None)
110
+ logger = get_flock_logger("webapp.execution.regular_run")
98
111
 
99
- # It's better to get flock from app_state here to validate before calling service
100
- current_flock: Flock | None = getattr(request.app.state, 'flock_instance', None)
101
-
102
- if not current_flock:
112
+ if not current_flock_from_state:
113
+ logger.error("HTMX Run (Regular): No Flock loaded in app_state.")
103
114
  return HTMLResponse("<p class='error'>No Flock loaded to run.</p>")
104
115
 
105
116
  form_data = await request.form()
106
117
  start_agent_name = form_data.get("start_agent_name")
107
118
 
108
119
  if not start_agent_name:
120
+ logger.warning("HTMX Run (Regular): Starting agent not selected.")
109
121
  return HTMLResponse("<p class='error'>Starting agent not selected.</p>")
110
122
 
111
- agent = current_flock.agents.get(start_agent_name)
123
+ agent = current_flock_from_state.agents.get(start_agent_name)
112
124
  if not agent:
125
+ logger.error(f"HTMX Run (Regular): Agent '{start_agent_name}' not found in Flock '{current_flock_from_state.name}'.")
113
126
  return HTMLResponse(
114
127
  f"<p class='error'>Agent '{start_agent_name}' not found in the current Flock.</p>"
115
128
  )
@@ -119,46 +132,91 @@ async def htmx_run_flock(
119
132
  try:
120
133
  parsed_spec = parse_schema(agent.input)
121
134
  for name, type_str, _ in parsed_spec:
122
- form_field_name = f"agent_input_{name}" # Matches the name in _dynamic_input_form_content.html
135
+ form_field_name = f"agent_input_{name}"
123
136
  raw_value = form_data.get(form_field_name)
124
-
125
137
  if raw_value is None and "bool" in type_str.lower(): inputs[name] = False; continue
126
138
  if raw_value is None: inputs[name] = None; continue
127
-
128
- if "int" in type_str.lower():
129
- try: inputs[name] = int(raw_value)
130
- except ValueError: return HTMLResponse(f"<p class='error'>Invalid integer for '{name}'.</p>")
131
- elif "float" in type_str.lower():
132
- try: inputs[name] = float(raw_value)
133
- except ValueError: return HTMLResponse(f"<p class='error'>Invalid float for '{name}'.</p>")
134
- elif "bool" in type_str.lower():
135
- inputs[name] = raw_value.lower() in ["true", "on", "1", "yes"]
136
- elif "list" in type_str.lower() or "dict" in type_str.lower():
137
- try: inputs[name] = json.loads(raw_value)
138
- except json.JSONDecodeError: return HTMLResponse(f"<p class='error'>Invalid JSON for '{name}'.</p>")
139
+ if "int" in type_str.lower(): inputs[name] = int(raw_value)
140
+ elif "float" in type_str.lower(): inputs[name] = float(raw_value)
141
+ elif "bool" in type_str.lower(): inputs[name] = raw_value.lower() in ["true", "on", "1", "yes"]
142
+ elif "list" in type_str.lower() or "dict" in type_str.lower(): inputs[name] = json.loads(raw_value)
139
143
  else: inputs[name] = raw_value
140
- except Exception as e:
141
- return HTMLResponse(f"<p class='error'>Error processing inputs for {start_agent_name}: {e}</p>")
144
+ except ValueError as ve:
145
+ logger.error(f"HTMX Run (Regular): Input parsing error for agent '{start_agent_name}': {ve}", exc_info=True)
146
+ return HTMLResponse(f"<p class='error'>Invalid input format: {ve!s}</p>")
147
+ except Exception as e_parse:
148
+ logger.error(f"HTMX Run (Regular): Error processing inputs for '{start_agent_name}': {e_parse}", exc_info=True)
149
+ return HTMLResponse(f"<p class='error'>Error processing inputs for {start_agent_name}: {e_parse}</p>")
150
+
151
+ result_data = await run_current_flock_service(start_agent_name, inputs, request.app.state)
152
+ raw_json_for_template = json.dumps(result_data, indent=2)
153
+ # Unescape newlines for proper display in HTML <pre> tag
154
+ result_data_raw_json_str = raw_json_for_template.replace('\\n', '\n')
142
155
 
156
+ return templates.TemplateResponse(
157
+ "partials/_results_display.html",
158
+ {"request": request, "result": result_data, "result_raw_json": result_data_raw_json_str} # Pass both
159
+ )
160
+
161
+
162
+ # --- NEW ENDPOINT FOR SHARED RUNS ---
163
+ @router.post("/htmx/run-shared", response_class=HTMLResponse)
164
+ async def htmx_run_shared_flock(
165
+ request: Request,
166
+ share_id: str = Form(...),
167
+ ):
168
+ shared_logger = get_flock_logger("webapp.execution.shared_run_stateful")
169
+ form_data = await request.form()
170
+ start_agent_name = form_data.get("start_agent_name")
171
+
172
+ if not start_agent_name:
173
+ shared_logger.warning("HTMX Run Shared: Starting agent not selected.")
174
+ return HTMLResponse("<p class='error'>Starting agent not selected for shared run.</p>")
175
+
176
+ inputs: dict[str, Any] = {}
143
177
  try:
144
- # Pass request.app.state to the service function
145
- result_data = await run_current_flock_service(start_agent_name, inputs, request.app.state)
178
+ shared_flocks_store = getattr(request.app.state, 'shared_flocks', {})
179
+ temp_flock = shared_flocks_store.get(share_id)
146
180
 
147
- try:
148
- result_data = pydantic_to_dict(result_data)
149
- try: json.dumps(result_data)
150
- except (TypeError, ValueError) as ser_e:
151
- result_data = f"Error: Result contains non-serializable data: {ser_e!s}\nOriginal result: {result_data!s}"
152
- except Exception as proc_e:
153
- result_data = f"Error: Failed to process result data: {proc_e!s}"
154
-
155
- return templates.TemplateResponse(
156
- "partials/_results_display.html",
157
- {"request": request, "result_data": result_data},
158
- )
181
+ if not temp_flock:
182
+ shared_logger.error(f"HTMX Run Shared: Flock instance for share_id '{share_id}' not found in app.state.")
183
+ return HTMLResponse(f"<p class='error'>Shared session not found or expired. Please try accessing the shared link again.</p>")
184
+
185
+ shared_logger.info(f"HTMX Run Shared: Successfully retrieved pre-loaded Flock '{temp_flock.name}' for agent '{start_agent_name}' (share_id: {share_id}).")
186
+
187
+ agent = temp_flock.agents.get(start_agent_name)
188
+ if not agent:
189
+ shared_logger.error(f"HTMX Run Shared: Agent '{start_agent_name}' not found in shared Flock '{temp_flock.name}'.")
190
+ return HTMLResponse(f"<p class='error'>Agent '{start_agent_name}' not found in the provided shared Flock definition.</p>")
191
+
192
+ if agent.input and isinstance(agent.input, str):
193
+ parsed_spec = parse_schema(agent.input)
194
+ for name, type_str, _ in parsed_spec:
195
+ form_field_name = f"agent_input_{name}"
196
+ raw_value = form_data.get(form_field_name)
197
+ if raw_value is None and "bool" in type_str.lower(): inputs[name] = False; continue
198
+ if raw_value is None: inputs[name] = None; continue
199
+ if "int" in type_str.lower(): inputs[name] = int(raw_value)
200
+ elif "float" in type_str.lower(): inputs[name] = float(raw_value)
201
+ elif "bool" in type_str.lower(): inputs[name] = raw_value.lower() in ["true", "on", "1", "yes"]
202
+ elif "list" in type_str.lower() or "dict" in type_str.lower(): inputs[name] = json.loads(raw_value)
203
+ else: inputs[name] = raw_value
204
+
205
+ shared_logger.info(f"HTMX Run Shared: Executing agent '{start_agent_name}' in pre-loaded Flock '{temp_flock.name}'. Inputs: {list(inputs.keys())}")
206
+ result_data = await temp_flock.run_async(start_agent=start_agent_name, input=inputs, box_result=False)
207
+ raw_json_for_template = json.dumps(result_data, indent=2)
208
+ # Unescape newlines for proper display in HTML <pre> tag
209
+ result_data_raw_json_str = raw_json_for_template.replace('\\n', '\n')
210
+ shared_logger.info(f"HTMX Run Shared: Agent '{start_agent_name}' executed. Result keys: {list(result_data.keys()) if isinstance(result_data, dict) else 'N/A'}")
211
+
212
+ except ValueError as ve:
213
+ shared_logger.error(f"HTMX Run Shared: Input parsing error for '{start_agent_name}' (share_id: {share_id}): {ve}", exc_info=True)
214
+ return HTMLResponse(f"<p class='error'>Invalid input format: {ve!s}</p>")
159
215
  except Exception as e:
160
- error_message = f"Error during execution: {e!s}"
161
- return templates.TemplateResponse(
162
- "partials/_results_display.html",
163
- {"request": request, "result_data": error_message}, # Display error in the same partial
164
- )
216
+ shared_logger.error(f"HTMX Run Shared: Error during execution for '{start_agent_name}' (share_id: {share_id}): {e}", exc_info=True)
217
+ return HTMLResponse(f"<p class='error'>An unexpected error occurred: {e!s}</p>")
218
+
219
+ return templates.TemplateResponse(
220
+ "partials/_results_display.html",
221
+ {"request": request, "result": result_data, "result_raw_json": result_data_raw_json_str} # Pass both
222
+ )