Skip to content

Commit 98c1377

Browse files
committed
toggle implementation
1 parent 3d08c22 commit 98c1377

File tree

31 files changed

+959
-296
lines changed

31 files changed

+959
-296
lines changed

autogpt_platform/backend/backend/blocks/human_in_the_loop.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
BlockOutput,
1010
BlockSchemaInput,
1111
BlockSchemaOutput,
12+
BlockType,
1213
)
1314
from backend.data.execution import ExecutionStatus
1415
from backend.data.human_review import ReviewResult
@@ -61,17 +62,18 @@ def __init__(self):
6162
categories={BlockCategory.BASIC},
6263
input_schema=HumanInTheLoopBlock.Input,
6364
output_schema=HumanInTheLoopBlock.Output,
65+
block_type=BlockType.HUMAN_IN_THE_LOOP,
6466
test_input={
6567
"data": {"name": "John Doe", "age": 30},
6668
"name": "User profile data",
6769
"editable": True,
6870
},
6971
test_output=[
70-
("reviewed_data", {"name": "John Doe", "age": 30}),
7172
("status", "approved"),
72-
("review_message", ""),
73+
("reviewed_data", {"name": "John Doe", "age": 30}),
7374
],
7475
test_mock={
76+
"_is_safe_mode": lambda *_args, **_kwargs: True, # Mock safe mode check
7577
"get_or_create_human_review": lambda *_args, **_kwargs: ReviewResult(
7678
data={"name": "John Doe", "age": 30},
7779
status=ReviewStatus.APPROVED,
@@ -80,9 +82,36 @@ def __init__(self):
8082
node_exec_id="test-node-exec-id",
8183
),
8284
"update_node_execution_status": lambda *_args, **_kwargs: None,
85+
"update_review_processed_status": lambda *_args, **_kwargs: None,
8386
},
8487
)
8588

89+
async def _is_safe_mode(
90+
self, graph_id: str, user_id: str, graph_version: int
91+
) -> bool:
92+
"""Get the safe mode setting for a graph, defaulting to True (safe mode ON)."""
93+
graph = await get_database_manager_async_client().get_graph(
94+
graph_id=graph_id, user_id=user_id, version=graph_version
95+
)
96+
if graph and graph.settings:
97+
return graph.settings.safe_mode
98+
return True
99+
100+
async def get_or_create_human_review(self, **kwargs):
101+
return await get_database_manager_async_client().get_or_create_human_review(
102+
**kwargs
103+
)
104+
105+
async def update_node_execution_status(self, **kwargs):
106+
return await async_update_node_execution_status(
107+
db_client=get_database_manager_async_client(), **kwargs
108+
)
109+
110+
async def update_review_processed_status(self, node_exec_id: str, processed: bool):
111+
return await get_database_manager_async_client().update_review_processed_status(
112+
node_exec_id, processed
113+
)
114+
86115
async def run(
87116
self,
88117
input_data: Input,
@@ -99,13 +128,22 @@ async def run(
99128
100129
This method uses one function to handle the complete workflow - checking existing reviews
101130
and creating pending ones as needed.
131+
132+
If safe_mode is disabled, this block will automatically approve the data without requiring human intervention.
102133
"""
103-
try:
104-
logger.debug(f"HITL block executing for node {node_exec_id}")
134+
if not await self._is_safe_mode(graph_id, user_id, graph_version):
135+
logger.info(
136+
f"HITL block skipping review for node {node_exec_id} - safe mode disabled"
137+
)
138+
# Automatically approve the data
139+
yield "status", "approved"
140+
yield "reviewed_data", input_data.data
141+
yield "review_message", "Auto-approved (safe mode disabled)"
142+
return
105143

144+
try:
106145
# Use the data layer to handle the complete workflow
107-
db_client = get_database_manager_async_client()
108-
result = await db_client.get_or_create_human_review(
146+
result = await self.get_or_create_human_review(
109147
user_id=user_id,
110148
node_exec_id=node_exec_id,
111149
graph_exec_id=graph_exec_id,
@@ -128,8 +166,7 @@ async def run(
128166
# Set node status to REVIEW so execution manager can't mark it as COMPLETED
129167
# The VALID_STATUS_TRANSITIONS will then prevent any unwanted status changes
130168
# Use the proper wrapper function to ensure websocket events are published
131-
await async_update_node_execution_status(
132-
db_client=db_client,
169+
await self.update_node_execution_status(
133170
exec_id=node_exec_id,
134171
status=ExecutionStatus.REVIEW,
135172
)
@@ -144,7 +181,7 @@ async def run(
144181
# Review is complete (approved or rejected) - check if unprocessed
145182
if not result.processed:
146183
# Mark as processed before yielding
147-
await db_client.update_review_processed_status(
184+
await self.update_review_processed_status(
148185
node_exec_id=node_exec_id, processed=True
149186
)
150187

autogpt_platform/backend/backend/blocks/time_blocks.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
BlockSchemaInput,
1515
BlockSchemaOutput,
1616
)
17-
from backend.data.execution import UserContext
18-
from backend.data.model import SchemaField
17+
from backend.data.model import USER_TIMEZONE_NOT_SET, SchemaField
18+
from backend.util.clients import get_database_manager_async_client
1919

2020
# Shared timezone literal type for all time/date blocks
2121
TimezoneLiteral = Literal[
@@ -62,6 +62,13 @@
6262
logger = logging.getLogger(__name__)
6363

6464

65+
async def _get_effective_timezone(user_id: str) -> str:
66+
user = await get_database_manager_async_client().get_user_by_id(user_id)
67+
if user and user.timezone and user.timezone != USER_TIMEZONE_NOT_SET:
68+
return user.timezone
69+
return "UTC"
70+
71+
6572
def _get_timezone(
6673
format_type: Any, # Any format type with timezone and use_user_timezone attributes
6774
user_timezone: str | None,
@@ -137,6 +144,10 @@ class TimeISO8601Format(BaseModel):
137144

138145

139146
class GetCurrentTimeBlock(Block):
147+
async def _get_user_timezone(self, user_id: str) -> str:
148+
"""Get the effective timezone for a user, defaulting to UTC if not set."""
149+
return await _get_effective_timezone(user_id)
150+
140151
class Input(BlockSchemaInput):
141152
trigger: str = SchemaField(
142153
description="Trigger any data to output the current time"
@@ -185,13 +196,14 @@ def __init__(self):
185196
lambda t: "T" in t and ("+" in t or "Z" in t),
186197
), # Check for ISO format with timezone
187198
],
199+
test_mock={
200+
"_get_user_timezone": lambda *args, **kwargs: "UTC",
201+
},
188202
)
189203

190-
async def run(
191-
self, input_data: Input, *, user_context: UserContext, **kwargs
192-
) -> BlockOutput:
193-
# Extract timezone from user_context (always present)
194-
effective_timezone = user_context.timezone
204+
async def run(self, input_data: Input, *, user_id: str, **kwargs) -> BlockOutput:
205+
# Get user timezone from database
206+
effective_timezone = await self._get_user_timezone(user_id)
195207

196208
# Get the appropriate timezone
197209
tz = _get_timezone(input_data.format_type, effective_timezone)
@@ -227,6 +239,9 @@ class DateISO8601Format(BaseModel):
227239

228240

229241
class GetCurrentDateBlock(Block):
242+
async def _get_user_timezone(self, user_id: str) -> str:
243+
return await _get_effective_timezone(user_id)
244+
230245
class Input(BlockSchemaInput):
231246
trigger: str = SchemaField(
232247
description="Trigger any data to output the current date"
@@ -296,12 +311,13 @@ def __init__(self):
296311
and t[7] == "-", # ISO date format YYYY-MM-DD
297312
),
298313
],
314+
test_mock={
315+
"_get_user_timezone": lambda *args, **kwargs: "UTC",
316+
},
299317
)
300318

301-
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
302-
# Extract timezone from user_context (required keyword argument)
303-
user_context: UserContext = kwargs["user_context"]
304-
effective_timezone = user_context.timezone
319+
async def run(self, input_data: Input, *, user_id: str, **kwargs) -> BlockOutput:
320+
effective_timezone = await self._get_user_timezone(user_id)
305321

306322
try:
307323
offset = int(input_data.offset)
@@ -338,6 +354,10 @@ class ISO8601Format(BaseModel):
338354

339355

340356
class GetCurrentDateAndTimeBlock(Block):
357+
async def _get_user_timezone(self, user_id: str) -> str:
358+
"""Get the effective timezone for a user, defaulting to UTC if not set."""
359+
return await _get_effective_timezone(user_id)
360+
341361
class Input(BlockSchemaInput):
342362
trigger: str = SchemaField(
343363
description="Trigger any data to output the current date and time"
@@ -402,12 +422,13 @@ def __init__(self):
402422
< timedelta(seconds=10), # 10 seconds error margin for ISO format.
403423
),
404424
],
425+
test_mock={
426+
"_get_user_timezone": lambda *args, **kwargs: "UTC",
427+
},
405428
)
406429

407-
async def run(self, input_data: Input, **kwargs) -> BlockOutput:
408-
# Extract timezone from user_context (required keyword argument)
409-
user_context: UserContext = kwargs["user_context"]
410-
effective_timezone = user_context.timezone
430+
async def run(self, input_data: Input, *, user_id: str, **kwargs) -> BlockOutput:
431+
effective_timezone = await self._get_user_timezone(user_id)
411432

412433
# Get the appropriate timezone
413434
tz = _get_timezone(input_data.format_type, effective_timezone)

autogpt_platform/backend/backend/data/block.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class BlockType(Enum):
7171
AGENT = "Agent"
7272
AI = "AI"
7373
AYRSHARE = "Ayrshare"
74+
HUMAN_IN_THE_LOOP = "Human In The Loop"
7475

7576

7677
class BlockCategory(Enum):
@@ -796,3 +797,12 @@ def get_io_block_ids() -> Sequence[str]:
796797
for id, B in get_blocks().items()
797798
if B().block_type in (BlockType.INPUT, BlockType.OUTPUT)
798799
]
800+
801+
802+
@cached(ttl_seconds=3600)
803+
def get_human_in_the_loop_block_ids() -> Sequence[str]:
804+
return [
805+
id
806+
for id, B in get_blocks().items()
807+
if B().block_type == BlockType.HUMAN_IN_THE_LOOP
808+
]

autogpt_platform/backend/backend/data/credit_test.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from backend.blocks.llm import AITextGeneratorBlock
88
from backend.data.block import get_block
99
from backend.data.credit import BetaUserCredit, UsageTransactionMetadata
10-
from backend.data.execution import NodeExecutionEntry, UserContext
10+
from backend.data.execution import NodeExecutionEntry
1111
from backend.data.user import DEFAULT_USER_ID
1212
from backend.executor.utils import block_usage_cost
1313
from backend.integrations.credentials_store import openai_credentials
@@ -86,7 +86,6 @@ async def test_block_credit_usage(server: SpinTestServer):
8686
"type": openai_credentials.type,
8787
},
8888
},
89-
user_context=UserContext(timezone="UTC"),
9089
),
9190
)
9291
assert spending_amount_1 > 0
@@ -101,7 +100,6 @@ async def test_block_credit_usage(server: SpinTestServer):
101100
node_exec_id="test_node_exec",
102101
block_id=AITextGeneratorBlock().id,
103102
inputs={"model": "gpt-4-turbo", "api_key": "owned_api_key"},
104-
user_context=UserContext(timezone="UTC"),
105103
),
106104
)
107105
assert spending_amount_2 == 0

autogpt_platform/backend/backend/data/execution.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,6 @@ def from_db(_graph_exec: AgentGraphExecution):
365365

366366
def to_graph_execution_entry(
367367
self,
368-
user_context: "UserContext",
369368
compiled_nodes_input_masks: Optional[NodesInputMasks] = None,
370369
parent_graph_exec_id: Optional[str] = None,
371370
):
@@ -375,7 +374,6 @@ def to_graph_execution_entry(
375374
graph_version=self.graph_version or 0,
376375
graph_exec_id=self.id,
377376
nodes_input_masks=compiled_nodes_input_masks,
378-
user_context=user_context,
379377
parent_graph_exec_id=parent_graph_exec_id,
380378
)
381379

@@ -448,9 +446,7 @@ def from_db(_node_exec: AgentNodeExecution, user_id: Optional[str] = None):
448446
end_time=_node_exec.endedTime,
449447
)
450448

451-
def to_node_execution_entry(
452-
self, user_context: "UserContext"
453-
) -> "NodeExecutionEntry":
449+
def to_node_execution_entry(self) -> "NodeExecutionEntry":
454450
return NodeExecutionEntry(
455451
user_id=self.user_id,
456452
graph_exec_id=self.graph_exec_id,
@@ -460,7 +456,6 @@ def to_node_execution_entry(
460456
node_id=self.node_id,
461457
block_id=self.block_id,
462458
inputs=self.input_data,
463-
user_context=user_context,
464459
)
465460

466461

@@ -1099,19 +1094,12 @@ async def get_latest_node_execution(
10991094
# ----------------- Execution Infrastructure ----------------- #
11001095

11011096

1102-
class UserContext(BaseModel):
1103-
"""Generic user context for graph execution containing user-specific settings."""
1104-
1105-
timezone: str
1106-
1107-
11081097
class GraphExecutionEntry(BaseModel):
11091098
user_id: str
11101099
graph_exec_id: str
11111100
graph_id: str
11121101
graph_version: int
11131102
nodes_input_masks: Optional[NodesInputMasks] = None
1114-
user_context: UserContext
11151103
parent_graph_exec_id: Optional[str] = None
11161104

11171105

@@ -1124,7 +1112,6 @@ class NodeExecutionEntry(BaseModel):
11241112
node_id: str
11251113
block_id: str
11261114
inputs: BlockInput
1127-
user_context: UserContext
11281115

11291116

11301117
class ExecutionQueue(Generic[T]):

0 commit comments

Comments
 (0)