ob-metaflow-extensions 1.1.162rc0__tar.gz → 1.1.163rc1__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 ob-metaflow-extensions might be problematic. Click here for more details.

Files changed (89) hide show
  1. {ob-metaflow-extensions-1.1.162rc0/ob_metaflow_extensions.egg-info → ob_metaflow_extensions-1.1.163rc1}/PKG-INFO +2 -2
  2. ob_metaflow_extensions-1.1.163rc1/metaflow_extensions/outerbounds/plugins/ollama/__init__.py +225 -0
  3. ob_metaflow_extensions-1.1.163rc1/metaflow_extensions/outerbounds/plugins/ollama/ollama.py +1924 -0
  4. ob_metaflow_extensions-1.1.163rc1/metaflow_extensions/outerbounds/plugins/ollama/status_card.py +292 -0
  5. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1/ob_metaflow_extensions.egg-info}/PKG-INFO +1 -1
  6. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/ob_metaflow_extensions.egg-info/SOURCES.txt +1 -0
  7. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/setup.py +2 -2
  8. ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/plugins/ollama/__init__.py +0 -89
  9. ob-metaflow-extensions-1.1.162rc0/metaflow_extensions/outerbounds/plugins/ollama/ollama.py +0 -813
  10. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/README.md +0 -0
  11. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/__init__.py +0 -0
  12. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/config/__init__.py +0 -0
  13. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/__init__.py +0 -0
  14. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/apps/__init__.py +0 -0
  15. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/apps/app_utils.py +0 -0
  16. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/apps/consts.py +0 -0
  17. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/apps/deploy_decorator.py +0 -0
  18. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/apps/supervisord_utils.py +0 -0
  19. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/auth_server.py +0 -0
  20. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/card_utilities/__init__.py +0 -0
  21. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/card_utilities/async_cards.py +0 -0
  22. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/card_utilities/extra_components.py +0 -0
  23. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/card_utilities/injector.py +0 -0
  24. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/__init__.py +0 -0
  25. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/coreweave.py +0 -0
  26. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/checkpoint_datastores/nebius.py +0 -0
  27. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/__init__.py +0 -0
  28. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/baker.py +0 -0
  29. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/docker_environment.py +0 -0
  30. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery.py +0 -0
  31. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_cli.py +0 -0
  32. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/fast_bakery/fast_bakery_decorator.py +0 -0
  33. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/kubernetes/__init__.py +0 -0
  34. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/kubernetes/kubernetes_client.py +0 -0
  35. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/kubernetes/pod_killer.py +0 -0
  36. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nim/card.py +0 -0
  37. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nim/nim_decorator.py +0 -0
  38. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nim/nim_manager.py +0 -0
  39. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nim/utils.py +0 -0
  40. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/__init__.py +0 -0
  41. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/constants.py +0 -0
  42. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/exceptions.py +0 -0
  43. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/heartbeat_store.py +0 -0
  44. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf.py +0 -0
  45. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_cli.py +0 -0
  46. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/nvcf_decorator.py +0 -0
  47. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvcf/utils.py +0 -0
  48. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/__init__.py +0 -0
  49. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/exceptions.py +0 -0
  50. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/nvct.py +0 -0
  51. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/nvct_cli.py +0 -0
  52. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/nvct_decorator.py +0 -0
  53. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/nvct_runner.py +0 -0
  54. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/nvct/utils.py +0 -0
  55. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/ollama/constants.py +0 -0
  56. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/ollama/exceptions.py +0 -0
  57. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/perimeters.py +0 -0
  58. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/profilers/deco_injector.py +0 -0
  59. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/profilers/gpu_profile_decorator.py +0 -0
  60. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/secrets/__init__.py +0 -0
  61. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/secrets/secrets.py +0 -0
  62. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowflake/__init__.py +0 -0
  63. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowflake/snowflake.py +0 -0
  64. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/__init__.py +0 -0
  65. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark.py +0 -0
  66. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_cli.py +0 -0
  67. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_client.py +0 -0
  68. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_decorator.py +0 -0
  69. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_exceptions.py +0 -0
  70. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_job.py +0 -0
  71. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/snowpark/snowpark_service_spec.py +0 -0
  72. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/tensorboard/__init__.py +0 -0
  73. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/plugins/torchtune/__init__.py +0 -0
  74. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/profilers/__init__.py +0 -0
  75. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/profilers/gpu.py +0 -0
  76. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/remote_config.py +0 -0
  77. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/__init__.py +0 -0
  78. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/global_aliases_for_metaflow_package.py +0 -0
  79. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/ob_internal.py +0 -0
  80. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/azure/__init__.py +0 -0
  81. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/gcp/__init__.py +0 -0
  82. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/kubernetes/__init__.py +0 -0
  83. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/ollama/__init__.py +0 -0
  84. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/snowflake/__init__.py +0 -0
  85. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/metaflow_extensions/outerbounds/toplevel/plugins/torchtune/__init__.py +0 -0
  86. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/ob_metaflow_extensions.egg-info/dependency_links.txt +0 -0
  87. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/ob_metaflow_extensions.egg-info/requires.txt +0 -0
  88. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/ob_metaflow_extensions.egg-info/top_level.txt +0 -0
  89. {ob-metaflow-extensions-1.1.162rc0 → ob_metaflow_extensions-1.1.163rc1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
- Name: ob-metaflow-extensions
3
- Version: 1.1.162rc0
2
+ Name: ob_metaflow_extensions
3
+ Version: 1.1.163rc1
4
4
  Summary: Outerbounds Platform Extensions for Metaflow
5
5
  Author: Outerbounds, Inc.
6
6
  License: Commercial
@@ -0,0 +1,225 @@
1
+ from metaflow.decorators import StepDecorator
2
+ from metaflow import current
3
+ import functools
4
+ import os
5
+ import threading
6
+
7
+ from .ollama import OllamaManager, OllamaRequestInterceptor
8
+ from .status_card import OllamaStatusCard
9
+ from ..card_utilities.injector import CardDecoratorInjector
10
+
11
+ __mf_promote_submodules__ = ["plugins.ollama"]
12
+
13
+
14
+ class OllamaDecorator(StepDecorator, CardDecoratorInjector):
15
+ """
16
+ This decorator is used to run Ollama APIs as Metaflow task sidecars.
17
+
18
+ User code call
19
+ --------------
20
+ @ollama(
21
+ models=[...],
22
+ ...
23
+ )
24
+
25
+ Valid backend options
26
+ ---------------------
27
+ - 'local': Run as a separate process on the local task machine.
28
+ - (TODO) 'managed': Outerbounds hosts and selects compute provider.
29
+ - (TODO) 'remote': Spin up separate instance to serve Ollama models.
30
+
31
+ Valid model options
32
+ -------------------
33
+ Any model here https://ollama.com/search, e.g. 'llama3.2', 'llama3.3'
34
+
35
+ Parameters
36
+ ----------
37
+ models: list[str]
38
+ List of Ollama containers running models in sidecars.
39
+ backend: str
40
+ Determines where and how to run the Ollama process.
41
+ force_pull: bool
42
+ Whether to run `ollama pull` no matter what, or first check the remote cache in Metaflow datastore for this model key.
43
+ cache_update_policy: str
44
+ Cache update policy: "auto", "force", or "never".
45
+ force_cache_update: bool
46
+ Simple override for "force" cache update policy.
47
+ debug: bool
48
+ Whether to turn on verbose debugging logs.
49
+ circuit_breaker_config: dict
50
+ Configuration for circuit breaker protection. Keys: failure_threshold, recovery_timeout, reset_timeout.
51
+ timeout_config: dict
52
+ Configuration for various operation timeouts. Keys: pull, stop, health_check, install, server_startup.
53
+ """
54
+
55
+ name = "ollama"
56
+ defaults = {
57
+ "models": [],
58
+ "backend": "local",
59
+ "force_pull": False,
60
+ "cache_update_policy": "auto", # "auto", "force", "never"
61
+ "force_cache_update": False, # Simple override for "force"
62
+ "debug": False,
63
+ "circuit_breaker_config": {
64
+ "failure_threshold": 3,
65
+ "recovery_timeout": 60,
66
+ "reset_timeout": 30,
67
+ },
68
+ "timeout_config": {
69
+ "pull": 600, # 10 minutes for model pulls
70
+ "stop": 30, # 30 seconds for model stops
71
+ "health_check": 5, # 5 seconds for health checks
72
+ "install": 60, # 1 minute for Ollama installation
73
+ "server_startup": 300, # 5 minutes for server startup
74
+ },
75
+ "card_refresh_interval": 10, # seconds - how often to update the status card
76
+ }
77
+
78
+ def step_init(
79
+ self, flow, graph, step_name, decorators, environment, flow_datastore, logger
80
+ ):
81
+ super().step_init(
82
+ flow, graph, step_name, decorators, environment, flow_datastore, logger
83
+ )
84
+ self.flow_datastore_backend = flow_datastore._storage_impl
85
+
86
+ # Attach the ollama status card
87
+ self.attach_card_decorator(
88
+ flow,
89
+ step_name,
90
+ "ollama_status",
91
+ "blank",
92
+ refresh_interval=self.attributes["card_refresh_interval"],
93
+ )
94
+
95
+ def task_decorate(
96
+ self, step_func, flow, graph, retry_count, max_user_code_retries, ubf_context
97
+ ):
98
+ @functools.wraps(step_func)
99
+ def ollama_wrapper():
100
+ self.ollama_manager = None
101
+ self.request_interceptor = None
102
+ self.status_card = None
103
+ self.card_monitor_thread = None
104
+
105
+ try:
106
+ # Initialize status card and monitoring
107
+ self.status_card = OllamaStatusCard(
108
+ refresh_interval=self.attributes["card_refresh_interval"]
109
+ )
110
+
111
+ # Start card monitoring in background
112
+ def monitor_card():
113
+ try:
114
+ self.status_card.on_startup(current.card["ollama_status"])
115
+
116
+ while not getattr(
117
+ self.card_monitor_thread, "_stop_event", False
118
+ ):
119
+ try:
120
+ # Trigger card update with current data
121
+ self.status_card.on_update(
122
+ current.card["ollama_status"], None
123
+ )
124
+ import time
125
+
126
+ time.sleep(self.attributes["card_refresh_interval"])
127
+ except Exception as e:
128
+ if self.attributes["debug"]:
129
+ print(f"[@ollama] Card monitoring error: {e}")
130
+ break
131
+ except Exception as e:
132
+ if self.attributes["debug"]:
133
+ print(f"[@ollama] Card monitor thread error: {e}")
134
+ self.status_card.on_error(current.card["ollama_status"], str(e))
135
+
136
+ self.card_monitor_thread = threading.Thread(
137
+ target=monitor_card, daemon=True
138
+ )
139
+ self.card_monitor_thread._stop_event = False
140
+ self.card_monitor_thread.start()
141
+
142
+ # Initialize OllamaManager with status card
143
+ self.ollama_manager = OllamaManager(
144
+ models=self.attributes["models"],
145
+ backend=self.attributes["backend"],
146
+ flow_datastore_backend=self.flow_datastore_backend,
147
+ force_pull=self.attributes["force_pull"],
148
+ cache_update_policy=self.attributes["cache_update_policy"],
149
+ force_cache_update=self.attributes["force_cache_update"],
150
+ debug=self.attributes["debug"],
151
+ circuit_breaker_config=self.attributes["circuit_breaker_config"],
152
+ timeout_config=self.attributes["timeout_config"],
153
+ status_card=self.status_card,
154
+ )
155
+
156
+ # Install request protection by monkey-patching ollama package
157
+ self.request_interceptor = OllamaRequestInterceptor(
158
+ self.ollama_manager.circuit_breaker, self.attributes["debug"]
159
+ )
160
+ self.request_interceptor.install_protection()
161
+
162
+ if self.attributes["debug"]:
163
+ print(
164
+ "[@ollama] OllamaManager initialized and request protection installed"
165
+ )
166
+
167
+ except Exception as e:
168
+ if self.status_card:
169
+ self.status_card.add_event(
170
+ "error", f"Initialization failed: {str(e)}"
171
+ )
172
+ try:
173
+ self.status_card.on_error(current.card["ollama_status"], str(e))
174
+ except:
175
+ pass
176
+ print(f"[@ollama] Error initializing OllamaManager: {e}")
177
+ raise
178
+
179
+ try:
180
+ if self.status_card:
181
+ self.status_card.add_event("info", "Starting user step function")
182
+ step_func()
183
+ if self.status_card:
184
+ self.status_card.add_event(
185
+ "success", "User step function completed successfully"
186
+ )
187
+ finally:
188
+ # Remove request protection first (before terminating models)
189
+ if self.request_interceptor:
190
+ self.request_interceptor.remove_protection()
191
+ if self.attributes["debug"]:
192
+ print("[@ollama] Request protection removed")
193
+
194
+ # Then cleanup ollama manager (while card monitoring is still active)
195
+ if self.ollama_manager:
196
+ self.ollama_manager.terminate_models()
197
+
198
+ # Give the card a moment to render the final shutdown events
199
+ if self.card_monitor_thread and self.status_card:
200
+ import time
201
+
202
+ # Trigger one final card update to capture all shutdown events
203
+ try:
204
+ self.status_card.on_update(current.card["ollama_status"], None)
205
+ except Exception as e:
206
+ if self.attributes["debug"]:
207
+ print(f"[@ollama] Final card update error: {e}")
208
+ time.sleep(2) # Allow final events to be rendered
209
+
210
+ # Now stop card monitoring
211
+ if self.card_monitor_thread:
212
+ self.card_monitor_thread._stop_event = True
213
+
214
+ if self.ollama_manager and self.attributes["debug"]:
215
+ print(
216
+ f"[@ollama] process statuses: {self.ollama_manager.processes}"
217
+ )
218
+ print(
219
+ f"[@ollama] process runtime stats: {self.ollama_manager.stats}"
220
+ )
221
+ print(
222
+ f"[@ollama] Circuit Breaker status: {self.ollama_manager.circuit_breaker.get_status()}"
223
+ )
224
+
225
+ return ollama_wrapper