process-gpt-agent-sdk 0.2.11__py3-none-any.whl → 0.3.4__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 process-gpt-agent-sdk might be problematic. Click here for more details.

@@ -1,19 +0,0 @@
1
- processgpt_agent_sdk/__init__.py,sha256=zQHVsiKXSn5cZ1OqhptxbOfoNLaq_VkAIECjRLnmW60,371
2
- processgpt_agent_sdk/server.py,sha256=RXehJ8RS1ctbyK03MmZp6nWd5HTGBmI6bMITcVxiVug,15268
3
- processgpt_agent_sdk/simulator.py,sha256=CxWjBH0BGmmAvBvzYveoSToB7LeJqIC6phT7o81WQB8,10194
4
- processgpt_agent_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- processgpt_agent_sdk/core/database.py,sha256=lvhIwNnM9ObOPDLDArUbfXZLXJwfx2PSzSCplN6BTTc,16871
6
- processgpt_agent_sdk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- processgpt_agent_sdk/tools/human_query_tool.py,sha256=jI5lJJ-JoW-QRYY8GPkrMBPIFgN_pb4NtS4LQyC9ulk,11082
8
- processgpt_agent_sdk/tools/knowledge_tools.py,sha256=xa0zncJPjmbd0bo5fjMUF6xY45SYlc0a1PSVx1aTtVs,9143
9
- processgpt_agent_sdk/tools/safe_tool_loader.py,sha256=3PN6bwcTiyzjh_NqOVmn3I4P4VUm6UsZZaIGgGv1Qsc,7872
10
- processgpt_agent_sdk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- processgpt_agent_sdk/utils/context_manager.py,sha256=1_WYpBg7jVlQKn_6_7Ep7mAqCeXeLcxoykdyVCD5fp0,1849
12
- processgpt_agent_sdk/utils/crewai_event_listener.py,sha256=PutCKn3L6Au9nRbUDBV2tv3DZXLz24T4HQWJaA0_Dxk,9360
13
- processgpt_agent_sdk/utils/event_handler.py,sha256=_S_xUMGEp9GUMIkWpJiW7j5lywRAwJzn9CF82cmtKbQ,2951
14
- processgpt_agent_sdk/utils/logger.py,sha256=jW4z0Wn88vPfXkOVg5l8VA6O4544OhnqrU17M2zr_Ss,3390
15
- processgpt_agent_sdk/utils/summarizer.py,sha256=f3AnbeMkTG-KffFrM9aiBxl0a01VMi8M1hYlS7rfcNo,5889
16
- process_gpt_agent_sdk-0.2.11.dist-info/METADATA,sha256=E5UJXgmuq7ayK3GjO6lVG6cK9T9u-E3H41dagHstYN8,34136
17
- process_gpt_agent_sdk-0.2.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- process_gpt_agent_sdk-0.2.11.dist-info/top_level.txt,sha256=Xe6zrj3_3Vv7d0pl5RRtenVUckwOVBVLQn2P03j5REo,21
19
- process_gpt_agent_sdk-0.2.11.dist-info/RECORD,,
File without changes
@@ -1,464 +0,0 @@
1
- import os
2
- import json
3
- import asyncio
4
- import socket
5
- import uuid
6
- from typing import Any, Dict, List, Optional, Tuple, Callable, TypeVar
7
-
8
- from dotenv import load_dotenv
9
- from supabase import Client, create_client
10
- import logging
11
- import random
12
-
13
- T = TypeVar("T")
14
-
15
- from ..utils.logger import handle_application_error, write_log_message
16
-
17
- # ============================================================================
18
- # Utility: 재시도 헬퍼 및 유틸
19
- # 설명: 동기 DB 호출을 안전하게 재시도 (지수 백오프 + 지터) 및 유틸
20
- # ============================================================================
21
-
22
- async def _async_retry(
23
- fn: Callable[[], T],
24
- *,
25
- name: str,
26
- retries: int = 3,
27
- base_delay: float = 0.8,
28
- fallback: Optional[Callable[[], T]] = None,
29
- ) -> Optional[T]:
30
- """지수 백오프+jitter로 재시도하고 실패 시 fallback/None 반환."""
31
- last_err: Optional[Exception] = None
32
- for attempt in range(1, retries + 1):
33
- try:
34
- return await asyncio.to_thread(fn)
35
- except Exception as e:
36
- last_err = e
37
- jitter = random.uniform(0, 0.3)
38
- delay = base_delay * (2 ** (attempt - 1)) + jitter
39
- write_log_message(f"{name} 재시도 {attempt}/{retries} (delay={delay:.2f}s): {e}", level=logging.WARNING)
40
- await asyncio.sleep(delay)
41
- write_log_message(f"{name} 최종 실패: {last_err}", level=logging.ERROR)
42
- if fallback is not None:
43
- try:
44
- fb_val = fallback()
45
- write_log_message(f"{name} 폴백 사용", level=logging.WARNING)
46
- return fb_val
47
- except Exception as e:
48
- write_log_message(f"{name} 폴백 실패: {e}", level=logging.ERROR)
49
- return None
50
-
51
-
52
- def _is_valid_uuid(value: str) -> bool:
53
- """UUID 문자열 형식 검증 (v1~v8 포함)"""
54
- try:
55
- uuid.UUID(value)
56
- return True
57
- except Exception:
58
- return False
59
-
60
-
61
- # ============================================================================
62
- # DB 연결/클라이언트
63
- # 설명: 환경 변수 로드, Supabase 클라이언트 초기화/반환, 컨슈머 식별자
64
- # ============================================================================
65
- _supabase_client: Optional[Client] = None
66
-
67
-
68
- def initialize_db() -> None:
69
- """환경변수 로드 및 Supabase 클라이언트 초기화"""
70
- global _supabase_client
71
- if _supabase_client is not None:
72
- return
73
- if os.getenv("ENV") != "production":
74
- load_dotenv()
75
- supabase_url = os.getenv("SUPABASE_URL") or os.getenv("SUPABASE_KEY_URL")
76
- supabase_key = os.getenv("SUPABASE_SERVICE_KEY") or os.getenv("SUPABASE_KEY") or os.getenv("SUPABASE_ANON_KEY")
77
- if not supabase_url or not supabase_key:
78
- raise RuntimeError("SUPABASE_URL 및 SUPABASE_KEY가 필요합니다")
79
- _supabase_client = create_client(supabase_url, supabase_key)
80
-
81
-
82
- def get_db_client() -> Client:
83
- """초기화된 Supabase 클라이언트 반환."""
84
- if _supabase_client is None:
85
- raise RuntimeError("DB 미초기화: initialize_db() 먼저 호출")
86
- return _supabase_client
87
-
88
-
89
- def get_consumer_id() -> str:
90
- """파드/프로세스 식별자 생성(CONSUMER_ID>HOST:PID)."""
91
- env_consumer = os.getenv("CONSUMER_ID")
92
- if env_consumer:
93
- return env_consumer
94
- host = socket.gethostname()
95
- pid = os.getpid()
96
- return f"{host}:{pid}"
97
-
98
-
99
- # ============================================================================
100
- # 데이터 조회
101
- # 설명: TODOLIST 테이블 조회, 완료 output 목록 조회, 이벤트 조회, 폼 조회, 테넌트 MCP 설정 조회, 사용자 및 에이전트 조회
102
- # ============================================================================
103
- async def polling_pending_todos(agent_orch: str, consumer: str) -> Optional[Dict[str, Any]]:
104
- """TODOLIST 테이블에서 대기중인 워크아이템을 조회"""
105
- def _call():
106
- supabase = get_db_client()
107
- consumer_id = socket.gethostname()
108
- env = (os.getenv("ENV") or "").lower()
109
-
110
- if env == "dev":
111
- # 개발 환경: 특정 테넌트(uengine)만 폴링
112
- resp = supabase.rpc(
113
- "fetch_pending_task_dev",
114
- {"p_limit": 1, "p_consumer": consumer_id, "p_tenant_id": "uengine"},
115
- ).execute()
116
- else:
117
- # 운영/기타 환경: 기존 로직 유지
118
- resp = supabase.rpc(
119
- "fetch_pending_task",
120
- {"p_limit": 1, "p_consumer": consumer_id},
121
- ).execute()
122
-
123
- rows = resp.data or []
124
- return rows[0] if rows else None
125
-
126
- resp = await _async_retry(_call, name="polling_pending_todos", fallback=lambda: None)
127
- if not resp:
128
- return None
129
- return resp
130
-
131
-
132
- async def fetch_todo_by_id(todo_id: str) -> Optional[Dict[str, Any]]:
133
- """특정 todo id로 todolist의 단건을 조회"""
134
- if not todo_id:
135
- return None
136
- def _call():
137
- client = get_db_client()
138
- return (
139
- client.table("todolist").select("*").eq("id", todo_id).single().execute()
140
- )
141
-
142
- resp = await _async_retry(_call, name="fetch_todo_by_id")
143
- if not resp or not resp.data:
144
- return None
145
- return resp.data
146
-
147
-
148
- async def fetch_done_data(proc_inst_id: Optional[str]) -> List[Any]:
149
- """proc_inst_id로 완료된 워크아이템의 output 목록을 조회"""
150
- if not proc_inst_id:
151
- return []
152
- def _call():
153
- client = get_db_client()
154
- return client.rpc("fetch_done_data", {"p_proc_inst_id": proc_inst_id}).execute()
155
-
156
- resp = await _async_retry(_call, name="fetch_done_data", fallback=lambda: None)
157
- if not resp:
158
- return []
159
- return [row.get("output") for row in (resp.data or []) if row.get("output")]
160
-
161
-
162
- def fetch_human_response_sync(job_id: str) -> Optional[Dict[str, Any]]:
163
- """events에서 특정 job_id의 human_response 조회"""
164
- if not job_id:
165
- return None
166
- try:
167
- client = get_db_client()
168
- resp = (
169
- client
170
- .table("events")
171
- .select("*")
172
- .eq("job_id", job_id)
173
- .eq("event_type", "human_response")
174
- .execute()
175
- )
176
- rows = resp.data or []
177
- return rows[0] if rows else None
178
- except Exception as e:
179
- handle_application_error("fetch_human_response_sync 실패", e, raise_error=False)
180
- return None
181
-
182
-
183
- async def fetch_task_status(todo_id: str) -> Optional[str]:
184
- """todo의 draft_status를 조회한다."""
185
- def _call():
186
- client = get_db_client()
187
- return (
188
- client.table("todolist").select("draft_status").eq("id", todo_id).single().execute()
189
- )
190
-
191
- resp = await _async_retry(_call, name="fetch_task_status")
192
- if not resp or not resp.data:
193
- return None
194
- return resp.data.get("draft_status")
195
-
196
-
197
-
198
- async def fetch_all_agents() -> List[Dict[str, Any]]:
199
- """모든 에이전트 목록을 정규화하여 반환한다."""
200
- def _call():
201
- client = get_db_client()
202
- return (
203
- client.table("users")
204
- .select("id, username, role, goal, persona, tools, profile, model, tenant_id, is_agent")
205
- .eq("is_agent", True)
206
- .execute()
207
- )
208
-
209
- resp = await _async_retry(_call, name="fetch_all_agents")
210
- rows = resp.data or [] if resp else []
211
- normalized: List[Dict[str, Any]] = []
212
- for row in rows:
213
- normalized.append(
214
- {
215
- "id": row.get("id"),
216
- "name": row.get("username"),
217
- "role": row.get("role"),
218
- "goal": row.get("goal"),
219
- "persona": row.get("persona"),
220
- "tools": row.get("tools") or "mem0",
221
- "profile": row.get("profile"),
222
- "model": row.get("model"),
223
- "tenant_id": row.get("tenant_id"),
224
- }
225
- )
226
- return normalized
227
-
228
-
229
- async def fetch_agent_data(user_ids: str) -> List[Dict[str, Any]]:
230
- """TODOLIST의 user_id 값으로, 역할로 지정된 에이전트를 조회하고 정규화해 반환한다."""
231
-
232
- raw_ids = [x.strip() for x in (user_ids or "").split(",") if x.strip()]
233
- valid_ids = [x for x in raw_ids if _is_valid_uuid(x)]
234
-
235
- if not valid_ids:
236
- return await fetch_all_agents()
237
-
238
- def _call():
239
- client = get_db_client()
240
- resp = (
241
- client
242
- .table("users")
243
- .select("id, username, role, goal, persona, tools, profile, model, tenant_id, is_agent")
244
- .in_("id", valid_ids)
245
- .eq("is_agent", True)
246
- .execute()
247
- )
248
- rows = resp.data or []
249
- normalized: List[Dict[str, Any]] = []
250
- for row in rows:
251
- normalized.append(
252
- {
253
- "id": row.get("id"),
254
- "name": row.get("username"),
255
- "role": row.get("role"),
256
- "goal": row.get("goal"),
257
- "persona": row.get("persona"),
258
- "tools": row.get("tools") or "mem0",
259
- "profile": row.get("profile"),
260
- "model": row.get("model"),
261
- "tenant_id": row.get("tenant_id"),
262
- }
263
- )
264
- return normalized
265
-
266
- result = await _async_retry(_call, name="fetch_agent_data", fallback=lambda: [])
267
-
268
- if not result:
269
- return await fetch_all_agents()
270
-
271
- return result
272
-
273
-
274
- async def fetch_form_types(tool_val: str, tenant_id: str) -> Tuple[str, List[Dict[str, Any]], Optional[str]]:
275
- """폼 타입 정의를 조회해 (form_id, fields, html)로 반환한다."""
276
- form_id = tool_val[12:] if tool_val.startswith("formHandler:") else tool_val
277
-
278
- def _call():
279
- client = get_db_client()
280
- resp = (
281
- client
282
- .table("form_def")
283
- .select("fields_json, html")
284
- .eq("id", form_id)
285
- .eq("tenant_id", tenant_id)
286
- .execute()
287
- )
288
- fields_json = resp.data[0].get("fields_json") if resp.data else None
289
- form_html = resp.data[0].get("html") if resp.data else None
290
- if not fields_json:
291
- return form_id, [{"key": form_id, "type": "default", "text": ""}], form_html
292
- return form_id, fields_json, form_html
293
-
294
- resp = await _async_retry(
295
- _call,
296
- name="fetch_form_types",
297
- fallback=lambda: (form_id, [{"key": form_id, "type": "default", "text": ""}], None),
298
- )
299
- return resp if resp else (form_id, [{"key": form_id, "type": "default", "text": ""}], None)
300
-
301
-
302
- async def fetch_tenant_mcp_config(tenant_id: str) -> Optional[Dict[str, Any]]:
303
- """테넌트 MCP 설정을 조회해 반환한다."""
304
- def _call():
305
- client = get_db_client()
306
- return client.table("tenants").select("mcp").eq("id", tenant_id).single().execute()
307
-
308
- resp = await _async_retry(_call, name="fetch_tenant_mcp_config", fallback=lambda: None)
309
- return resp.data.get("mcp") if resp and resp.data else None
310
-
311
-
312
- async def fetch_human_users_by_proc_inst_id(proc_inst_id: str) -> str:
313
- """proc_inst_id로 현재 프로세스의 모든 사용자 이메일 목록을 쉼표로 반환한다."""
314
- if not proc_inst_id:
315
- return ""
316
-
317
- def _sync():
318
- try:
319
- supabase = get_db_client()
320
-
321
- resp = (
322
- supabase
323
- .table('todolist')
324
- .select('user_id')
325
- .eq('proc_inst_id', proc_inst_id)
326
- .execute()
327
- )
328
-
329
- if not resp.data:
330
- return ""
331
-
332
- all_user_ids = set()
333
- for row in resp.data:
334
- user_id = row.get('user_id', '')
335
- if user_id:
336
- ids = [id.strip() for id in user_id.split(',') if id.strip()]
337
- all_user_ids.update(ids)
338
-
339
- if not all_user_ids:
340
- return ""
341
-
342
- human_user_emails = []
343
- for user_id in all_user_ids:
344
- if not _is_valid_uuid(user_id):
345
- continue
346
-
347
- user_resp = (
348
- supabase
349
- .table('users')
350
- .select('id, email, is_agent')
351
- .eq('id', user_id)
352
- .execute()
353
- )
354
-
355
- if user_resp.data:
356
- user = user_resp.data[0]
357
- is_agent = user.get('is_agent')
358
- if not is_agent:
359
- email = (user.get('email') or '').strip()
360
- if email:
361
- human_user_emails.append(email)
362
-
363
- return ','.join(human_user_emails)
364
-
365
- except Exception as e:
366
- handle_application_error("사용자조회오류", e, raise_error=False)
367
- return ""
368
-
369
- return await asyncio.to_thread(_sync)
370
-
371
-
372
- # ============================================================================
373
- # 데이터 저장
374
- # 설명: 이벤트/알림/작업 결과 저장
375
- # ============================================================================
376
- async def record_event(payload: Dict[str, Any]) -> None:
377
- """UI용 events 테이블에 이벤트 기록 (전달된 payload 그대로 저장)"""
378
- def _call():
379
- client = get_db_client()
380
- return client.table("events").insert(payload).execute()
381
-
382
- resp = await _async_retry(_call, name="record_event", fallback=lambda: None)
383
- if resp is None:
384
- write_log_message("record_event 최종 실패(무시)", level=logging.WARNING)
385
-
386
-
387
-
388
- async def save_task_result(todo_id: str, result: Any, final: bool = False) -> None:
389
- """작업 결과를 저장한다(중간/최종)."""
390
- def _call():
391
- client = get_db_client()
392
- payload = result if isinstance(result, (dict, list)) else json.loads(json.dumps(result))
393
- return client.rpc(
394
- "save_task_result",
395
- {"p_todo_id": todo_id, "p_payload": payload, "p_final": final},
396
- ).execute()
397
-
398
- await _async_retry(_call, name="save_task_result", fallback=lambda: None)
399
-
400
-
401
- def save_notification(
402
- *,
403
- title: str,
404
- notif_type: str,
405
- description: Optional[str] = None,
406
- user_ids_csv: Optional[str] = None,
407
- tenant_id: Optional[str] = None,
408
- url: Optional[str] = None,
409
- from_user_id: Optional[str] = None,
410
- ) -> None:
411
- """notifications 테이블에 알림 저장"""
412
- try:
413
- # 대상 사용자가 없으면 작업 생략
414
- if not user_ids_csv:
415
- write_log_message(f"알림 저장 생략: 대상 사용자 없음 (user_ids_csv={user_ids_csv})")
416
- return
417
-
418
- supabase = get_db_client()
419
-
420
- user_ids: List[str] = [uid.strip() for uid in user_ids_csv.split(',') if uid and uid.strip()]
421
- if not user_ids:
422
- write_log_message(f"알림 저장 생략: 유효한 사용자 ID 없음 (user_ids_csv={user_ids_csv})")
423
- return
424
-
425
- rows: List[Dict[str, Any]] = []
426
- for uid in user_ids:
427
- rows.append(
428
- {
429
- "id": str(uuid.uuid4()), # UUID 자동 생성
430
- "user_id": uid,
431
- "tenant_id": tenant_id,
432
- "title": title,
433
- "description": description,
434
- "type": notif_type,
435
- "url": url,
436
- "from_user_id": from_user_id,
437
- }
438
- )
439
-
440
- supabase.table("notifications").insert(rows).execute()
441
- write_log_message(f"알림 저장 완료: {len(rows)}건")
442
- except Exception as e:
443
- handle_application_error("알림저장오류", e, raise_error=False)
444
-
445
- # ============================================================================
446
- # 상태 변경
447
- # 설명: 실패 작업 상태 업데이트
448
- # ============================================================================
449
-
450
- async def update_task_error(todo_id: str) -> None:
451
- """실패 작업의 상태를 FAILED로 갱신한다."""
452
- if not todo_id:
453
- return
454
- def _call():
455
- client = get_db_client()
456
- return (
457
- client
458
- .table('todolist')
459
- .update({'draft_status': 'FAILED', 'consumer': None})
460
- .eq('id', todo_id)
461
- .execute()
462
- )
463
-
464
- await _async_retry(_call, name="update_task_error", fallback=lambda: None)