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.
- process_gpt_agent_sdk-0.2.9.dist-info/METADATA +1026 -0
- process_gpt_agent_sdk-0.2.9.dist-info/RECORD +19 -0
- processgpt_agent_sdk/__init__.py +11 -7
- processgpt_agent_sdk/core/database.py +464 -464
- processgpt_agent_sdk/server.py +313 -292
- processgpt_agent_sdk/simulator.py +231 -0
- processgpt_agent_sdk/tools/human_query_tool.py +211 -211
- processgpt_agent_sdk/tools/knowledge_tools.py +206 -206
- processgpt_agent_sdk/tools/safe_tool_loader.py +209 -209
- processgpt_agent_sdk/utils/context_manager.py +45 -45
- processgpt_agent_sdk/utils/crewai_event_listener.py +205 -205
- processgpt_agent_sdk/utils/event_handler.py +72 -72
- processgpt_agent_sdk/utils/logger.py +73 -39
- processgpt_agent_sdk/utils/summarizer.py +146 -146
- process_gpt_agent_sdk-0.2.7.dist-info/METADATA +0 -378
- process_gpt_agent_sdk-0.2.7.dist-info/RECORD +0 -18
- {process_gpt_agent_sdk-0.2.7.dist-info → process_gpt_agent_sdk-0.2.9.dist-info}/WHEEL +0 -0
- {process_gpt_agent_sdk-0.2.7.dist-info → process_gpt_agent_sdk-0.2.9.dist-info}/top_level.txt +0 -0
|
@@ -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)
|