process-gpt-agent-sdk 0.2.7__py3-none-any.whl → 0.2.9__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,205 +1,205 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import json
5
- import uuid
6
- from datetime import datetime, timezone
7
- from typing import Any, Optional, Dict, List
8
-
9
- from crewai.utilities.events import CrewAIEventsBus, ToolUsageStartedEvent, ToolUsageFinishedEvent
10
- from crewai.utilities.events.task_events import TaskStartedEvent, TaskCompletedEvent
11
-
12
- from .logger import handle_application_error, write_log_message
13
- from .context_manager import todo_id_var, proc_id_var, crew_type_var, form_id_var, form_key_var
14
- from ..core.database import initialize_db, get_db_client
15
-
16
-
17
- class CrewAIEventLogger:
18
- """CrewAI 이벤트 로거 - Supabase 전용"""
19
-
20
- # =============================================================================
21
- # Initialization
22
- # =============================================================================
23
- def __init__(self):
24
- """Supabase 클라이언트를 초기화한다."""
25
- initialize_db()
26
- self.supabase = get_db_client()
27
- write_log_message("CrewAIEventLogger 초기화 완료")
28
-
29
- # =============================================================================
30
- # Job ID Generation
31
- # =============================================================================
32
- def _generate_job_id(self, event_obj: Any, source: Any = None) -> str:
33
- """이벤트 객체에서 Job ID 생성"""
34
- try:
35
- if hasattr(event_obj, "task") and hasattr(event_obj.task, "id"):
36
- return str(event_obj.task.id)
37
- if source and hasattr(source, "task") and hasattr(source.task, "id"):
38
- return str(source.task.id)
39
- except Exception:
40
- pass
41
- return "unknown"
42
-
43
- # =============================================================================
44
- # Record Creation
45
- # =============================================================================
46
- def _create_event_record(
47
- self,
48
- event_type: str,
49
- data: Dict[str, Any],
50
- job_id: str,
51
- crew_type: str,
52
- todo_id: Optional[str],
53
- proc_inst_id: Optional[str],
54
- ) -> Dict[str, Any]:
55
- """이벤트 레코드 생성"""
56
- return {
57
- "id": str(uuid.uuid4()),
58
- "job_id": job_id,
59
- "todo_id": todo_id,
60
- "proc_inst_id": proc_inst_id,
61
- "event_type": event_type,
62
- "crew_type": crew_type,
63
- "data": data,
64
- "timestamp": datetime.now(timezone.utc).isoformat(),
65
- }
66
-
67
- # =============================================================================
68
- # Parsing Helpers
69
- # =============================================================================
70
- def _parse_json_text(self, text: str) -> Any:
71
- """JSON 문자열을 객체로 파싱하거나 원본 반환"""
72
- try:
73
- return json.loads(text)
74
- except:
75
- return text
76
-
77
- def _parse_output(self, output: Any) -> Any:
78
- """output 또는 raw 텍스트를 파싱해 반환"""
79
- if not output:
80
- return ""
81
- text = getattr(output, "raw", None) or (output if isinstance(output, str) else "")
82
- return self._parse_json_text(text)
83
-
84
- def _parse_tool_args(self, args_text: str) -> Optional[str]:
85
- """tool_args에서 query 키 추출"""
86
- try:
87
- args = json.loads(args_text or "{}")
88
- return args.get("query")
89
- except Exception:
90
- return None
91
-
92
- # =============================================================================
93
- # Formatting Helpers
94
- # =============================================================================
95
- def _format_plans_md(self, plans: List[Dict[str, Any]]) -> str:
96
- """list_of_plans_per_task 형식을 Markdown 문자열로 변환"""
97
- lines: List[str] = []
98
- for idx, item in enumerate(plans or [], 1):
99
- task = item.get("task", "")
100
- plan = item.get("plan", "")
101
- lines.append(f"## {idx}. {task}")
102
- lines.append("")
103
- if isinstance(plan, list):
104
- for line in plan:
105
- lines.append(str(line))
106
- elif isinstance(plan, str):
107
- for line in plan.split("\n"):
108
- lines.append(line)
109
- else:
110
- lines.append(str(plan))
111
- lines.append("")
112
- return "\n".join(lines).strip()
113
-
114
- # =============================================================================
115
- # Data Extraction
116
- # =============================================================================
117
- def _extract_event_data(self, event_obj: Any, source: Any = None) -> Dict[str, Any]:
118
- """이벤트 타입별 데이터 추출"""
119
- etype = getattr(event_obj, "type", None) or type(event_obj).__name__
120
- if etype == "task_started":
121
- agent = getattr(getattr(event_obj, "task", None), "agent", None)
122
- return {
123
- "role": getattr(agent, "role", "Unknown"),
124
- "goal": getattr(agent, "goal", "Unknown"),
125
- "agent_profile": getattr(agent, "profile", None) or "/images/chat-icon.png",
126
- "name": getattr(agent, "name", "Unknown"),
127
- }
128
- if etype == "task_completed":
129
- result = self._parse_output(getattr(event_obj, "output", None))
130
- if isinstance(result, dict) and "list_of_plans_per_task" in result:
131
- md = self._format_plans_md(result.get("list_of_plans_per_task") or [])
132
- return {"plans": md}
133
- return {"result": result}
134
-
135
- if etype in ("tool_usage_started", "tool_usage_finished") or str(etype).startswith("tool_"):
136
- return {
137
- "tool_name": getattr(event_obj, "tool_name", None),
138
- "query": self._parse_tool_args(getattr(event_obj, "tool_args", "")),
139
- }
140
- return {"info": f"Event type: {etype}"}
141
-
142
- # =============================================================================
143
- # Event Saving
144
- # =============================================================================
145
- def _save_event(self, record: Dict[str, Any]) -> None:
146
- """Supabase에 이벤트 레코드 저장 (간단 재시도 포함)"""
147
- payload = json.loads(json.dumps(record, default=str))
148
- for attempt in range(1, 4):
149
- try:
150
- self.supabase.table("events").insert(payload).execute()
151
- return
152
- except Exception as e:
153
- if attempt < 3:
154
- handle_application_error("이벤트저장오류(재시도)", e, raise_error=False)
155
- import time
156
- time.sleep(0.3 * attempt)
157
- continue
158
- handle_application_error("이벤트저장오류(최종)", e, raise_error=False)
159
- return
160
-
161
- # =============================================================================
162
- # Event Handling
163
- # =============================================================================
164
- def on_event(self, event_obj: Any, source: Any = None) -> None:
165
- """이벤트 수신부터 DB 저장까지 처리"""
166
- etype = getattr(event_obj, "type", None) or type(event_obj).__name__
167
- ALLOWED = {"task_started", "task_completed", "tool_usage_started", "tool_usage_finished"}
168
- if etype not in ALLOWED:
169
- return
170
- try:
171
- job_id = self._generate_job_id(event_obj, source)
172
- data = self._extract_event_data(event_obj, source)
173
- crew_type = crew_type_var.get() or "action"
174
- rec = self._create_event_record(etype, data, job_id, crew_type, todo_id_var.get(), proc_id_var.get())
175
- self._save_event(rec)
176
- write_log_message(f"[{etype}] [{job_id[:8]}] 저장 완료")
177
- except Exception as e:
178
- handle_application_error("이벤트처리오류", e, raise_error=False)
179
-
180
-
181
- # =============================================================================
182
- # CrewConfigManager
183
- # 설명: 이벤트 리스너를 프로세스 단위로 1회 등록
184
- # =============================================================================
185
- class CrewConfigManager:
186
- """글로벌 CrewAI 이벤트 리스너 등록 매니저"""
187
- _registered_by_pid: set[int] = set()
188
-
189
- def __init__(self) -> None:
190
- self.logger = CrewAIEventLogger()
191
- self._register_once_per_process()
192
-
193
- def _register_once_per_process(self) -> None:
194
- """현재 프로세스에만 한 번 이벤트 리스너를 등록한다."""
195
- try:
196
- pid = os.getpid()
197
- if pid in self._registered_by_pid:
198
- return
199
- bus = CrewAIEventsBus()
200
- for evt in (TaskStartedEvent, TaskCompletedEvent, ToolUsageStartedEvent, ToolUsageFinishedEvent):
201
- bus.on(evt)(lambda source, event, logger=self.logger: logger.on_event(event, source))
202
- self._registered_by_pid.add(pid)
203
- write_log_message("CrewAI event listeners 등록 완료")
204
- except Exception as e:
205
- handle_application_error("CrewAI 이벤트 버스 등록 실패", e, raise_error=False)
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import json
5
+ import uuid
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Optional, Dict, List
8
+
9
+ from crewai.utilities.events import CrewAIEventsBus, ToolUsageStartedEvent, ToolUsageFinishedEvent
10
+ from crewai.utilities.events.task_events import TaskStartedEvent, TaskCompletedEvent
11
+
12
+ from .logger import handle_application_error, write_log_message
13
+ from .context_manager import todo_id_var, proc_id_var, crew_type_var, form_id_var, form_key_var
14
+ from ..core.database import initialize_db, get_db_client
15
+
16
+
17
+ class CrewAIEventLogger:
18
+ """CrewAI 이벤트 로거 - Supabase 전용"""
19
+
20
+ # =============================================================================
21
+ # Initialization
22
+ # =============================================================================
23
+ def __init__(self):
24
+ """Supabase 클라이언트를 초기화한다."""
25
+ initialize_db()
26
+ self.supabase = get_db_client()
27
+ write_log_message("CrewAIEventLogger 초기화 완료")
28
+
29
+ # =============================================================================
30
+ # Job ID Generation
31
+ # =============================================================================
32
+ def _generate_job_id(self, event_obj: Any, source: Any = None) -> str:
33
+ """이벤트 객체에서 Job ID 생성"""
34
+ try:
35
+ if hasattr(event_obj, "task") and hasattr(event_obj.task, "id"):
36
+ return str(event_obj.task.id)
37
+ if source and hasattr(source, "task") and hasattr(source.task, "id"):
38
+ return str(source.task.id)
39
+ except Exception:
40
+ pass
41
+ return "unknown"
42
+
43
+ # =============================================================================
44
+ # Record Creation
45
+ # =============================================================================
46
+ def _create_event_record(
47
+ self,
48
+ event_type: str,
49
+ data: Dict[str, Any],
50
+ job_id: str,
51
+ crew_type: str,
52
+ todo_id: Optional[str],
53
+ proc_inst_id: Optional[str],
54
+ ) -> Dict[str, Any]:
55
+ """이벤트 레코드 생성"""
56
+ return {
57
+ "id": str(uuid.uuid4()),
58
+ "job_id": job_id,
59
+ "todo_id": todo_id,
60
+ "proc_inst_id": proc_inst_id,
61
+ "event_type": event_type,
62
+ "crew_type": crew_type,
63
+ "data": data,
64
+ "timestamp": datetime.now(timezone.utc).isoformat(),
65
+ }
66
+
67
+ # =============================================================================
68
+ # Parsing Helpers
69
+ # =============================================================================
70
+ def _parse_json_text(self, text: str) -> Any:
71
+ """JSON 문자열을 객체로 파싱하거나 원본 반환"""
72
+ try:
73
+ return json.loads(text)
74
+ except:
75
+ return text
76
+
77
+ def _parse_output(self, output: Any) -> Any:
78
+ """output 또는 raw 텍스트를 파싱해 반환"""
79
+ if not output:
80
+ return ""
81
+ text = getattr(output, "raw", None) or (output if isinstance(output, str) else "")
82
+ return self._parse_json_text(text)
83
+
84
+ def _parse_tool_args(self, args_text: str) -> Optional[str]:
85
+ """tool_args에서 query 키 추출"""
86
+ try:
87
+ args = json.loads(args_text or "{}")
88
+ return args.get("query")
89
+ except Exception:
90
+ return None
91
+
92
+ # =============================================================================
93
+ # Formatting Helpers
94
+ # =============================================================================
95
+ def _format_plans_md(self, plans: List[Dict[str, Any]]) -> str:
96
+ """list_of_plans_per_task 형식을 Markdown 문자열로 변환"""
97
+ lines: List[str] = []
98
+ for idx, item in enumerate(plans or [], 1):
99
+ task = item.get("task", "")
100
+ plan = item.get("plan", "")
101
+ lines.append(f"## {idx}. {task}")
102
+ lines.append("")
103
+ if isinstance(plan, list):
104
+ for line in plan:
105
+ lines.append(str(line))
106
+ elif isinstance(plan, str):
107
+ for line in plan.split("\n"):
108
+ lines.append(line)
109
+ else:
110
+ lines.append(str(plan))
111
+ lines.append("")
112
+ return "\n".join(lines).strip()
113
+
114
+ # =============================================================================
115
+ # Data Extraction
116
+ # =============================================================================
117
+ def _extract_event_data(self, event_obj: Any, source: Any = None) -> Dict[str, Any]:
118
+ """이벤트 타입별 데이터 추출"""
119
+ etype = getattr(event_obj, "type", None) or type(event_obj).__name__
120
+ if etype == "task_started":
121
+ agent = getattr(getattr(event_obj, "task", None), "agent", None)
122
+ return {
123
+ "role": getattr(agent, "role", "Unknown"),
124
+ "goal": getattr(agent, "goal", "Unknown"),
125
+ "agent_profile": getattr(agent, "profile", None) or "/images/chat-icon.png",
126
+ "name": getattr(agent, "name", "Unknown"),
127
+ }
128
+ if etype == "task_completed":
129
+ result = self._parse_output(getattr(event_obj, "output", None))
130
+ if isinstance(result, dict) and "list_of_plans_per_task" in result:
131
+ md = self._format_plans_md(result.get("list_of_plans_per_task") or [])
132
+ return {"plans": md}
133
+ return {"result": result}
134
+
135
+ if etype in ("tool_usage_started", "tool_usage_finished") or str(etype).startswith("tool_"):
136
+ return {
137
+ "tool_name": getattr(event_obj, "tool_name", None),
138
+ "query": self._parse_tool_args(getattr(event_obj, "tool_args", "")),
139
+ }
140
+ return {"info": f"Event type: {etype}"}
141
+
142
+ # =============================================================================
143
+ # Event Saving
144
+ # =============================================================================
145
+ def _save_event(self, record: Dict[str, Any]) -> None:
146
+ """Supabase에 이벤트 레코드 저장 (간단 재시도 포함)"""
147
+ payload = json.loads(json.dumps(record, default=str))
148
+ for attempt in range(1, 4):
149
+ try:
150
+ self.supabase.table("events").insert(payload).execute()
151
+ return
152
+ except Exception as e:
153
+ if attempt < 3:
154
+ handle_application_error("이벤트저장오류(재시도)", e, raise_error=False)
155
+ import time
156
+ time.sleep(0.3 * attempt)
157
+ continue
158
+ handle_application_error("이벤트저장오류(최종)", e, raise_error=False)
159
+ return
160
+
161
+ # =============================================================================
162
+ # Event Handling
163
+ # =============================================================================
164
+ def on_event(self, event_obj: Any, source: Any = None) -> None:
165
+ """이벤트 수신부터 DB 저장까지 처리"""
166
+ etype = getattr(event_obj, "type", None) or type(event_obj).__name__
167
+ ALLOWED = {"task_started", "task_completed", "tool_usage_started", "tool_usage_finished"}
168
+ if etype not in ALLOWED:
169
+ return
170
+ try:
171
+ job_id = self._generate_job_id(event_obj, source)
172
+ data = self._extract_event_data(event_obj, source)
173
+ crew_type = crew_type_var.get() or "action"
174
+ rec = self._create_event_record(etype, data, job_id, crew_type, todo_id_var.get(), proc_id_var.get())
175
+ self._save_event(rec)
176
+ write_log_message(f"[{etype}] [{job_id[:8]}] 저장 완료")
177
+ except Exception as e:
178
+ handle_application_error("이벤트처리오류", e, raise_error=False)
179
+
180
+
181
+ # =============================================================================
182
+ # CrewConfigManager
183
+ # 설명: 이벤트 리스너를 프로세스 단위로 1회 등록
184
+ # =============================================================================
185
+ class CrewConfigManager:
186
+ """글로벌 CrewAI 이벤트 리스너 등록 매니저"""
187
+ _registered_by_pid: set[int] = set()
188
+
189
+ def __init__(self) -> None:
190
+ self.logger = CrewAIEventLogger()
191
+ self._register_once_per_process()
192
+
193
+ def _register_once_per_process(self) -> None:
194
+ """현재 프로세스에만 한 번 이벤트 리스너를 등록한다."""
195
+ try:
196
+ pid = os.getpid()
197
+ if pid in self._registered_by_pid:
198
+ return
199
+ bus = CrewAIEventsBus()
200
+ for evt in (TaskStartedEvent, TaskCompletedEvent, ToolUsageStartedEvent, ToolUsageFinishedEvent):
201
+ bus.on(evt)(lambda source, event, logger=self.logger: logger.on_event(event, source))
202
+ self._registered_by_pid.add(pid)
203
+ write_log_message("CrewAI event listeners 등록 완료")
204
+ except Exception as e:
205
+ handle_application_error("CrewAI 이벤트 버스 등록 실패", e, raise_error=False)
@@ -1,72 +1,72 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, Dict
4
- import uuid
5
-
6
- from a2a.server.events import Event
7
- from .logger import handle_application_error, write_log_message
8
- from ..core.database import record_event, save_task_result
9
- from ..tools.safe_tool_loader import SafeToolLoader
10
-
11
-
12
- # =============================================================================
13
- # 이벤트 변환: Event 또는 dict를 표준 dict로 통일
14
- # =============================================================================
15
-
16
- def convert_event_to_dictionary(event: Event) -> Dict[str, Any]:
17
- """Event/dict를 표준 dict로 변환한다."""
18
- try:
19
- # 이미 dict로 전달된 경우 그대로 사용
20
- if isinstance(event, dict):
21
- return event
22
- # Event 객체면 공개 필드만 추출
23
- if hasattr(event, "__dict__"):
24
- return {k: v for k, v in event.__dict__.items() if not k.startswith("_")}
25
- # 알 수 없는 타입은 문자열로 보존
26
- return {"type": "event", "data": str(event)}
27
- except Exception as e:
28
- handle_application_error("event dict 변환 실패", e, raise_error=False)
29
- return {"type": "event", "data": str(event)}
30
-
31
-
32
- # =============================================================================
33
- # 이벤트 처리: type에 따라 저장 위치 분기
34
- # =============================================================================
35
-
36
- async def process_event_message(todo: Dict[str, Any], event: Event) -> None:
37
- """이벤트 타입별로 todolist/events에 저장하거나 리소스 정리."""
38
- try:
39
- data = convert_event_to_dictionary(event)
40
- evt_type = str(data.get("type") or data.get("event_type") or "").lower()
41
-
42
- # done: 종료 이벤트 → 기록 후 MCP 정리
43
- if evt_type == "done":
44
- payload = data.get("data") or {}
45
- if isinstance(payload, dict) and "id" not in payload:
46
- payload["id"] = str(uuid.uuid4())
47
- await record_event(payload)
48
- try:
49
- SafeToolLoader.shutdown_all_adapters()
50
- write_log_message("MCP 리소스 정리 완료")
51
- except Exception as ce:
52
- handle_application_error("MCP 리소스 정리 실패", ce, raise_error=False)
53
- return
54
-
55
- # output: 결과 저장만 수행
56
- if evt_type == "output":
57
- payload = data.get("data") or {}
58
- is_final = bool(payload.get("final") or payload.get("is_final")) if isinstance(payload, dict) else False
59
- content = payload.get("content") or payload.get("data") if isinstance(payload, dict) else payload
60
- await save_task_result(str(todo.get("id")), content, final=is_final)
61
- return
62
-
63
- # event : 일반 이벤트 저장 (워커 데이터 그대로 보존)
64
- if evt_type == "event":
65
- payload = data.get("data") or {}
66
- if isinstance(payload, dict) and "id" not in payload:
67
- payload["id"] = str(uuid.uuid4())
68
- await record_event(payload)
69
- return
70
-
71
- except Exception as e:
72
- handle_application_error("process_event_message 처리 실패", e, raise_error=False)
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict
4
+ import uuid
5
+
6
+ from a2a.server.events import Event
7
+ from .logger import handle_application_error, write_log_message
8
+ from ..core.database import record_event, save_task_result
9
+ from ..tools.safe_tool_loader import SafeToolLoader
10
+
11
+
12
+ # =============================================================================
13
+ # 이벤트 변환: Event 또는 dict를 표준 dict로 통일
14
+ # =============================================================================
15
+
16
+ def convert_event_to_dictionary(event: Event) -> Dict[str, Any]:
17
+ """Event/dict를 표준 dict로 변환한다."""
18
+ try:
19
+ # 이미 dict로 전달된 경우 그대로 사용
20
+ if isinstance(event, dict):
21
+ return event
22
+ # Event 객체면 공개 필드만 추출
23
+ if hasattr(event, "__dict__"):
24
+ return {k: v for k, v in event.__dict__.items() if not k.startswith("_")}
25
+ # 알 수 없는 타입은 문자열로 보존
26
+ return {"type": "event", "data": str(event)}
27
+ except Exception as e:
28
+ handle_application_error("event dict 변환 실패", e, raise_error=False)
29
+ return {"type": "event", "data": str(event)}
30
+
31
+
32
+ # =============================================================================
33
+ # 이벤트 처리: type에 따라 저장 위치 분기
34
+ # =============================================================================
35
+
36
+ async def process_event_message(todo: Dict[str, Any], event: Event) -> None:
37
+ """이벤트 타입별로 todolist/events에 저장하거나 리소스 정리."""
38
+ try:
39
+ data = convert_event_to_dictionary(event)
40
+ evt_type = str(data.get("type") or data.get("event_type") or "").lower()
41
+
42
+ # done: 종료 이벤트 → 기록 후 MCP 정리
43
+ if evt_type == "done":
44
+ payload = data.get("data") or {}
45
+ if isinstance(payload, dict) and "id" not in payload:
46
+ payload["id"] = str(uuid.uuid4())
47
+ await record_event(payload)
48
+ try:
49
+ SafeToolLoader.shutdown_all_adapters()
50
+ write_log_message("MCP 리소스 정리 완료")
51
+ except Exception as ce:
52
+ handle_application_error("MCP 리소스 정리 실패", ce, raise_error=False)
53
+ return
54
+
55
+ # output: 결과 저장만 수행
56
+ if evt_type == "output":
57
+ payload = data.get("data") or {}
58
+ is_final = bool(payload.get("final") or payload.get("is_final")) if isinstance(payload, dict) else False
59
+ content = payload.get("content") or payload.get("data") if isinstance(payload, dict) else payload
60
+ await save_task_result(str(todo.get("id")), content, final=is_final)
61
+ return
62
+
63
+ # event : 일반 이벤트 저장 (워커 데이터 그대로 보존)
64
+ if evt_type == "event":
65
+ payload = data.get("data") or {}
66
+ if isinstance(payload, dict) and "id" not in payload:
67
+ payload["id"] = str(uuid.uuid4())
68
+ await record_event(payload)
69
+ return
70
+
71
+ except Exception as e:
72
+ handle_application_error("process_event_message 처리 실패", e, raise_error=False)