process-gpt-agent-sdk 0.3.18__py3-none-any.whl → 0.3.20__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.3.18.dist-info → process_gpt_agent_sdk-0.3.20.dist-info}/METADATA +1 -1
- process_gpt_agent_sdk-0.3.20.dist-info/RECORD +8 -0
- processgpt_agent_sdk/__init__.py +22 -4
- processgpt_agent_sdk/database.py +169 -51
- processgpt_agent_sdk/processgpt_agent_framework.py +100 -66
- processgpt_agent_sdk/utils.py +12 -15
- process_gpt_agent_sdk-0.3.18.dist-info/RECORD +0 -8
- {process_gpt_agent_sdk-0.3.18.dist-info → process_gpt_agent_sdk-0.3.20.dist-info}/WHEEL +0 -0
- {process_gpt_agent_sdk-0.3.18.dist-info → process_gpt_agent_sdk-0.3.20.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
processgpt_agent_sdk/__init__.py,sha256=4-Wt-FRImZa1tG4CWx2ZtICcnTkKoAiqAfva9vLitLQ,991
|
|
2
|
+
processgpt_agent_sdk/database.py,sha256=4oQxJ3y28pPdUpvcQ80DEcFmcvKH37RszhSSZKf6tJU,12690
|
|
3
|
+
processgpt_agent_sdk/processgpt_agent_framework.py,sha256=PPAmWX1IV2SXTvLrkFXHgpuNw0xvKJP5vbXzW4GFx0Y,22940
|
|
4
|
+
processgpt_agent_sdk/utils.py,sha256=XNL-PId5xflUiTF3HQ3lOF264e4y85MXZJOfPix32Iw,8429
|
|
5
|
+
process_gpt_agent_sdk-0.3.20.dist-info/METADATA,sha256=hiif_fASGX7GRlU81ei1hL4M5PFbVqXUp5vx04IrGfI,21981
|
|
6
|
+
process_gpt_agent_sdk-0.3.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
process_gpt_agent_sdk-0.3.20.dist-info/top_level.txt,sha256=Xe6zrj3_3Vv7d0pl5RRtenVUckwOVBVLQn2P03j5REo,21
|
|
8
|
+
process_gpt_agent_sdk-0.3.20.dist-info/RECORD,,
|
processgpt_agent_sdk/__init__.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
from .processgpt_agent_framework import
|
|
1
|
+
from .processgpt_agent_framework import (
|
|
2
|
+
ProcessGPTAgentServer,
|
|
3
|
+
ProcessGPTRequestContext,
|
|
4
|
+
ProcessGPTEventQueue,
|
|
5
|
+
ContextPreparationError,
|
|
6
|
+
)
|
|
2
7
|
from .database import (
|
|
3
8
|
initialize_db,
|
|
4
9
|
get_consumer_id,
|
|
@@ -7,12 +12,21 @@ from .database import (
|
|
|
7
12
|
record_events_bulk,
|
|
8
13
|
save_task_result,
|
|
9
14
|
update_task_error,
|
|
10
|
-
|
|
15
|
+
fetch_form_def,
|
|
16
|
+
fetch_users_grouped,
|
|
17
|
+
fetch_email_users_by_proc_inst_id,
|
|
18
|
+
fetch_tenant_mcp,
|
|
19
|
+
)
|
|
20
|
+
from .utils import (
|
|
21
|
+
summarize_error_to_user,
|
|
22
|
+
summarize_feedback,
|
|
11
23
|
)
|
|
12
|
-
from .utils import summarize_error_to_user
|
|
13
24
|
|
|
14
25
|
__all__ = [
|
|
15
26
|
"ProcessGPTAgentServer",
|
|
27
|
+
"ProcessGPTRequestContext",
|
|
28
|
+
"ProcessGPTEventQueue",
|
|
29
|
+
"ContextPreparationError",
|
|
16
30
|
"initialize_db",
|
|
17
31
|
"get_consumer_id",
|
|
18
32
|
"polling_pending_todos",
|
|
@@ -20,6 +34,10 @@ __all__ = [
|
|
|
20
34
|
"record_events_bulk",
|
|
21
35
|
"save_task_result",
|
|
22
36
|
"update_task_error",
|
|
23
|
-
"
|
|
37
|
+
"fetch_form_def",
|
|
38
|
+
"fetch_users_grouped",
|
|
39
|
+
"fetch_email_users_by_proc_inst_id",
|
|
40
|
+
"fetch_tenant_mcp",
|
|
24
41
|
"summarize_error_to_user",
|
|
42
|
+
"summarize_feedback",
|
|
25
43
|
]
|
processgpt_agent_sdk/database.py
CHANGED
|
@@ -130,57 +130,27 @@ async def polling_pending_todos(agent_orch: str, consumer: str) -> Optional[Dict
|
|
|
130
130
|
).execute()
|
|
131
131
|
|
|
132
132
|
rows = resp.data or []
|
|
133
|
-
|
|
133
|
+
if not rows:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
row = rows[0]
|
|
137
|
+
# 빈 값들을 NULL로 변환
|
|
138
|
+
if row.get("feedback") in ([], {}):
|
|
139
|
+
row["feedback"] = None
|
|
140
|
+
if row.get("output") in ([], {}):
|
|
141
|
+
row["output"] = None
|
|
142
|
+
if row.get("draft") in ([], {}):
|
|
143
|
+
row["draft"] = None
|
|
144
|
+
|
|
145
|
+
return row
|
|
134
146
|
|
|
135
147
|
return await _async_retry(_call, name="polling_pending_todos", fallback=lambda: None)
|
|
136
148
|
|
|
137
|
-
# ------------------------------ Context Bundle ------------------------------
|
|
138
|
-
async def fetch_context_bundle(
|
|
139
|
-
proc_inst_id: str,
|
|
140
|
-
tenant_id: str,
|
|
141
|
-
tool_val: str,
|
|
142
|
-
user_ids: str,
|
|
143
|
-
) -> Tuple[str, Optional[Dict[str, Any]], Tuple[Optional[str], List[Dict[str, Any]], Optional[str]], List[Dict[str, Any]]]:
|
|
144
|
-
def _call():
|
|
145
|
-
client = get_db_client()
|
|
146
|
-
resp = client.rpc(
|
|
147
|
-
"fetch_context_bundle",
|
|
148
|
-
{
|
|
149
|
-
"p_proc_inst_id": proc_inst_id or "",
|
|
150
|
-
"p_tenant_id": tenant_id or "",
|
|
151
|
-
"p_tool": tool_val or "",
|
|
152
|
-
"p_user_ids": user_ids or "",
|
|
153
|
-
},
|
|
154
|
-
).execute()
|
|
155
|
-
rows = resp.data or []
|
|
156
|
-
row = rows[0] if rows else {}
|
|
157
|
-
notify = (row.get("notify_emails") or "").strip()
|
|
158
|
-
mcp = row.get("tenant_mcp") or None
|
|
159
|
-
form_id = row.get("form_id")
|
|
160
|
-
form_fields = row.get("form_fields")
|
|
161
|
-
form_html = row.get("form_html")
|
|
162
|
-
|
|
163
|
-
# form 정보가 없는 경우 자유형식 폼으로 처리
|
|
164
|
-
if not form_id or not form_fields:
|
|
165
|
-
form_id = "freeform"
|
|
166
|
-
form_fields = [{"key": "freeform", "type": "textarea", "text": "자유형식 입력", "placeholder": "원하는 내용을 자유롭게 입력해주세요."}]
|
|
167
|
-
form_html = None
|
|
168
|
-
agents = row.get("agents") or []
|
|
169
|
-
return notify, mcp, (form_id, form_fields, form_html), agents
|
|
170
|
-
|
|
171
|
-
try:
|
|
172
|
-
return await _async_retry(_call, name="fetch_context_bundle", fallback=lambda: ("", None, (None, [], None), []))
|
|
173
|
-
except Exception as e:
|
|
174
|
-
logger.error("fetch_context_bundle fatal: %s", str(e), exc_info=e)
|
|
175
|
-
return ("", None, (None, [], None), [])
|
|
176
149
|
|
|
177
150
|
# ------------------------------ Events & Results ------------------------------
|
|
178
151
|
async def record_events_bulk(payloads: List[Dict[str, Any]]) -> None:
|
|
179
|
-
"""
|
|
180
|
-
|
|
181
|
-
- 성공: 'record_events_bulk ok'
|
|
182
|
-
- 실패(최종): '❌ record_events_bulk failed' (개수 포함)
|
|
183
|
-
"""
|
|
152
|
+
"""이벤트 다건 저장 함수"""
|
|
153
|
+
|
|
184
154
|
if not payloads:
|
|
185
155
|
return
|
|
186
156
|
|
|
@@ -202,12 +172,8 @@ async def record_events_bulk(payloads: List[Dict[str, Any]]) -> None:
|
|
|
202
172
|
logger.info("record_events_bulk ok: count=%d", len(safe_list))
|
|
203
173
|
|
|
204
174
|
async def record_event(payload: Dict[str, Any]) -> None:
|
|
205
|
-
"""
|
|
206
|
-
|
|
207
|
-
- 성공: 'record_event ok'
|
|
208
|
-
- 실패(최종): '❌ record_event failed'
|
|
209
|
-
- 실패해도 워크플로우는 계속(요청사항)
|
|
210
|
-
"""
|
|
175
|
+
"""단건 이벤트 저장 함수"""
|
|
176
|
+
|
|
211
177
|
if not payload:
|
|
212
178
|
return
|
|
213
179
|
|
|
@@ -225,6 +191,8 @@ async def record_event(payload: Dict[str, Any]) -> None:
|
|
|
225
191
|
logger.info("record_event ok: event_type=%s", payload.get("event_type"))
|
|
226
192
|
|
|
227
193
|
async def save_task_result(todo_id: str, result: Any, final: bool = False) -> None:
|
|
194
|
+
"""결과 저장 함수"""
|
|
195
|
+
|
|
228
196
|
if not todo_id:
|
|
229
197
|
logger.error("save_task_result invalid todo_id: %s", str(todo_id))
|
|
230
198
|
return
|
|
@@ -251,6 +219,8 @@ async def save_task_result(todo_id: str, result: Any, final: bool = False) -> No
|
|
|
251
219
|
|
|
252
220
|
# ------------------------------ Failure Status ------------------------------
|
|
253
221
|
async def update_task_error(todo_id: str) -> None:
|
|
222
|
+
"""작업 실패 상태 업데이트 함수"""
|
|
223
|
+
|
|
254
224
|
if not todo_id:
|
|
255
225
|
return
|
|
256
226
|
|
|
@@ -263,3 +233,151 @@ async def update_task_error(todo_id: str) -> None:
|
|
|
263
233
|
logger.error("❌ update_task_error failed todo_id=%s", todo_id)
|
|
264
234
|
else:
|
|
265
235
|
logger.info("update_task_error ok todo_id=%s", todo_id)
|
|
236
|
+
|
|
237
|
+
# ============================== Prepare Context ==============================
|
|
238
|
+
|
|
239
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
240
|
+
|
|
241
|
+
async def fetch_form_def(tool_val: str, tenant_id: str) -> Tuple[str, List[Dict[str, Any]], Optional[str]]:
|
|
242
|
+
"""폼 정의 조회 함수"""
|
|
243
|
+
form_id = (tool_val or "").replace("formHandler:", "", 1)
|
|
244
|
+
|
|
245
|
+
def _call():
|
|
246
|
+
client = get_db_client()
|
|
247
|
+
resp = (
|
|
248
|
+
client.table("form_def")
|
|
249
|
+
.select("fields_json, html")
|
|
250
|
+
.eq("id", form_id)
|
|
251
|
+
.eq("tenant_id", tenant_id or "")
|
|
252
|
+
.execute()
|
|
253
|
+
)
|
|
254
|
+
data = (resp.data or [])
|
|
255
|
+
if not data:
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
row = data[0]
|
|
259
|
+
return {
|
|
260
|
+
"fields": row.get("fields_json"),
|
|
261
|
+
"html": row.get("html"),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
res = await _async_retry(_call, name="fetch_form_def")
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error("fetch_form_def fatal: %s", str(e), exc_info=e)
|
|
268
|
+
res = None
|
|
269
|
+
|
|
270
|
+
if not res or not res.get("fields"):
|
|
271
|
+
# 기본(자유형식) 폼
|
|
272
|
+
return (
|
|
273
|
+
form_id or "freeform",
|
|
274
|
+
[{"key": "freeform", "type": "textarea", "text": "자유형식 입력", "placeholder": "원하는 내용을 자유롭게 입력해주세요."}],
|
|
275
|
+
None,
|
|
276
|
+
)
|
|
277
|
+
return (form_id or "freeform", res["fields"], res.get("html"))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
async def fetch_users_grouped(user_ids: List[str]) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
281
|
+
"""해당 todo에서 사용자 목록과 에이전트 목록 조회하는는 함수"""
|
|
282
|
+
ids = [u for u in (user_ids or []) if u]
|
|
283
|
+
if not ids:
|
|
284
|
+
return ([], [])
|
|
285
|
+
|
|
286
|
+
def _call():
|
|
287
|
+
client = get_db_client()
|
|
288
|
+
resp = (
|
|
289
|
+
client.table("users")
|
|
290
|
+
.select("*")
|
|
291
|
+
.in_("id", ids)
|
|
292
|
+
.execute()
|
|
293
|
+
)
|
|
294
|
+
rows = resp.data or []
|
|
295
|
+
return rows
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
rows = await _async_retry(_call, name="fetch_users_grouped", fallback=lambda: [])
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error("fetch_users_grouped fatal: %s", str(e), exc_info=e)
|
|
301
|
+
rows = []
|
|
302
|
+
|
|
303
|
+
agents, users = [], []
|
|
304
|
+
for r in rows:
|
|
305
|
+
if r.get("is_agent") is True:
|
|
306
|
+
agents.append(r)
|
|
307
|
+
else:
|
|
308
|
+
users.append(r)
|
|
309
|
+
return (agents, users)
|
|
310
|
+
|
|
311
|
+
async def fetch_email_users_by_proc_inst_id(proc_inst_id: str) -> str:
|
|
312
|
+
"""proc_inst_id로 이메일 수집(사람만): todolist → users(in) 한 번에"""
|
|
313
|
+
if not proc_inst_id:
|
|
314
|
+
return ""
|
|
315
|
+
|
|
316
|
+
def _call():
|
|
317
|
+
client = get_db_client()
|
|
318
|
+
# 3-1) 해당 인스턴스의 user_id 수집(중복 제거)
|
|
319
|
+
tl = (
|
|
320
|
+
client.table("todolist")
|
|
321
|
+
.select("user_id")
|
|
322
|
+
.eq("proc_inst_id", proc_inst_id)
|
|
323
|
+
.execute()
|
|
324
|
+
)
|
|
325
|
+
ids_set = set()
|
|
326
|
+
for row in (tl.data or []):
|
|
327
|
+
uid_csv = (row.get("user_id") or "").strip()
|
|
328
|
+
if not uid_csv:
|
|
329
|
+
continue
|
|
330
|
+
# user_id는 문자열 CSV라고 전제
|
|
331
|
+
for uid in uid_csv.split(","):
|
|
332
|
+
u = uid.strip()
|
|
333
|
+
if u:
|
|
334
|
+
ids_set.add(u)
|
|
335
|
+
if not ids_set:
|
|
336
|
+
return []
|
|
337
|
+
|
|
338
|
+
# 3-2) 한 번의 IN 조회로 사람만 이메일 추출
|
|
339
|
+
ur = (
|
|
340
|
+
client.table("users")
|
|
341
|
+
.select("id, email, is_agent")
|
|
342
|
+
.in_("id", list(ids_set))
|
|
343
|
+
.eq("is_agent", False)
|
|
344
|
+
.execute()
|
|
345
|
+
)
|
|
346
|
+
emails = []
|
|
347
|
+
for u in (ur.data or []):
|
|
348
|
+
email = (u.get("email") or "").strip()
|
|
349
|
+
if email:
|
|
350
|
+
emails.append(email)
|
|
351
|
+
# 중복 제거 및 정렬(보기 좋게)
|
|
352
|
+
return sorted(set(emails))
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
emails = await _async_retry(_call, name="fetch_email_users_by_proc_inst_id", fallback=lambda: [])
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.error("fetch_email_users_by_proc_inst_id fatal: %s", str(e), exc_info=e)
|
|
358
|
+
emails = []
|
|
359
|
+
|
|
360
|
+
return ",".join(emails) if emails else ""
|
|
361
|
+
|
|
362
|
+
async def fetch_tenant_mcp(tenant_id: str) -> Optional[Dict[str, Any]]:
|
|
363
|
+
"""mcp 설정 조회 함수"""
|
|
364
|
+
if not tenant_id:
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def _call():
|
|
368
|
+
client = get_db_client()
|
|
369
|
+
return (
|
|
370
|
+
client.table("tenants")
|
|
371
|
+
.select("mcp")
|
|
372
|
+
.eq("id", tenant_id)
|
|
373
|
+
.single()
|
|
374
|
+
.execute()
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
resp = await _async_retry(_call, name="fetch_tenant_mcp", fallback=lambda: None)
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error("fetch_tenant_mcp fatal: %s", str(e), exc_info=e)
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
return resp.data.get("mcp") if resp and getattr(resp, "data", None) else None
|
|
@@ -17,11 +17,14 @@ from .database import (
|
|
|
17
17
|
initialize_db,
|
|
18
18
|
polling_pending_todos,
|
|
19
19
|
record_events_bulk,
|
|
20
|
-
record_event,
|
|
20
|
+
record_event,
|
|
21
21
|
save_task_result,
|
|
22
22
|
update_task_error,
|
|
23
23
|
get_consumer_id,
|
|
24
|
-
|
|
24
|
+
fetch_form_def,
|
|
25
|
+
fetch_users_grouped,
|
|
26
|
+
fetch_email_users_by_proc_inst_id,
|
|
27
|
+
fetch_tenant_mcp,
|
|
25
28
|
)
|
|
26
29
|
from .utils import summarize_error_to_user, summarize_feedback
|
|
27
30
|
|
|
@@ -100,69 +103,105 @@ class ProcessGPTRequestContext(RequestContext):
|
|
|
100
103
|
self._extra_context: Dict[str, Any] = {}
|
|
101
104
|
|
|
102
105
|
async def prepare_context(self) -> None:
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
- 실패 시: 더 이상 진행하지 않고 ContextPreparationError를 발생시켜
|
|
106
|
-
상위 경계에서 FAILED 처리(이벤트 기록 포함)를 단일 경로로 수행.
|
|
107
|
-
"""
|
|
108
|
-
logger.info("\n🔧 컨텍스트 준비 시작...")
|
|
109
|
-
|
|
110
|
-
# 1단계: 기본 정보 추출
|
|
106
|
+
"""익스큐터를 위한 컨텍스트 준비를 합니다."""
|
|
107
|
+
|
|
111
108
|
effective_proc_inst_id = self.row.get("root_proc_inst_id") or self.row.get("proc_inst_id")
|
|
112
109
|
tool_val = self.row.get("tool") or ""
|
|
113
110
|
tenant_id = self.row.get("tenant_id") or ""
|
|
114
111
|
user_ids = self.row.get("user_id") or ""
|
|
115
|
-
|
|
116
|
-
logger.info("📋 기본 정보 추출 완료 - proc_inst_id: %s, tool: %s, tenant: %s",
|
|
117
|
-
effective_proc_inst_id, tool_val, tenant_id)
|
|
118
112
|
|
|
119
113
|
try:
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
# 데이터베이스 조회
|
|
115
|
+
user_id_list = [u.strip() for u in (user_ids or '').split(',') if u.strip()]
|
|
116
|
+
notify_task = fetch_email_users_by_proc_inst_id(effective_proc_inst_id)
|
|
117
|
+
mcp_task = fetch_tenant_mcp(tenant_id)
|
|
118
|
+
form_task = fetch_form_def(tool_val, tenant_id)
|
|
119
|
+
users_task = fetch_users_grouped(user_id_list)
|
|
120
|
+
|
|
121
|
+
notify_emails, tenant_mcp, form_tuple, users_group = await asyncio.gather(
|
|
122
|
+
notify_task, mcp_task, form_task, users_task
|
|
124
123
|
)
|
|
125
124
|
form_id, form_fields, form_html = form_tuple
|
|
125
|
+
agents, users = users_group
|
|
126
|
+
|
|
127
|
+
logger.info("\n\n🔍 [데이터베이스 조회 결과]")
|
|
128
|
+
|
|
129
|
+
# Users 정보
|
|
130
|
+
if users:
|
|
131
|
+
user_info = []
|
|
132
|
+
for u in users[:5]:
|
|
133
|
+
name = u.get("name", u.get("user_name", "Unknown"))
|
|
134
|
+
email = u.get("email", "")
|
|
135
|
+
user_info.append(f"{name}({email})" if email else name)
|
|
136
|
+
logger.info("• Users (%d명): %s%s", len(users), ", ".join(user_info), "..." if len(users) > 5 else "")
|
|
137
|
+
else:
|
|
138
|
+
logger.info("• Users: 없음")
|
|
139
|
+
|
|
140
|
+
# Agents 정보
|
|
141
|
+
if agents:
|
|
142
|
+
agent_info = []
|
|
143
|
+
for a in agents:
|
|
144
|
+
name = a.get("name", a.get("agent_name", "Unknown"))
|
|
145
|
+
tools = a.get("tools", "")
|
|
146
|
+
tool_str = f"[{tools}]" if tools else ""
|
|
147
|
+
agent_info.append(f"{name}{tool_str}")
|
|
148
|
+
logger.info("• Agents (%d개): %s%s", len(agents), ", ".join(agent_info), "..." if len(agents) > 5 else "")
|
|
149
|
+
else:
|
|
150
|
+
logger.info("• Agents: 없음")
|
|
151
|
+
|
|
152
|
+
# Form 정보
|
|
153
|
+
if form_fields:
|
|
154
|
+
pretty_json = json.dumps(form_fields, ensure_ascii=False, separators=(',', ':'))
|
|
155
|
+
logger.info("• Form: %s (%d개 필드) - %s", form_id, len(form_fields), pretty_json)
|
|
156
|
+
else:
|
|
157
|
+
logger.info("• Form: %s (필드 없음)", form_id)
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
159
|
+
# Notify 정보
|
|
160
|
+
if notify_emails:
|
|
161
|
+
email_list = notify_emails.split(',') if ',' in notify_emails else [notify_emails]
|
|
162
|
+
logger.info("• Notify (%d개): %s", len(email_list),
|
|
163
|
+
", ".join(email_list[:3]) + ("..." if len(email_list) > 3 else ""))
|
|
164
|
+
else:
|
|
165
|
+
logger.info("• Notify: 없음")
|
|
166
|
+
|
|
167
|
+
# MCP 정보 - 상세 표시
|
|
168
|
+
if tenant_mcp:
|
|
169
|
+
logger.info("• %s 테넌트에 연결된 MCP 설정 정보가 존재합니다.", tenant_id)
|
|
170
|
+
else:
|
|
171
|
+
logger.info("• %s 테넌트에 연결된 MCP 설정 정보가 존재하지 않습니다.", tenant_id)
|
|
172
|
+
|
|
173
|
+
# 피드백 처리
|
|
174
|
+
feedback_data = self.row.get("feedback")
|
|
175
|
+
content_data = self.row.get("output") or self.row.get("draft")
|
|
176
|
+
summarized_feedback = ""
|
|
177
|
+
if feedback_data:
|
|
178
|
+
logger.info("\n\n📝 [피드백 처리]")
|
|
179
|
+
logger.info("• %d자 → AI 요약 중...", len(feedback_data))
|
|
180
|
+
summarized_feedback = await summarize_feedback(feedback_data, content_data)
|
|
181
|
+
logger.info("• 요약 완료: %d자", len(summarized_feedback))
|
|
182
|
+
|
|
183
|
+
# 컨텍스트 구성
|
|
184
|
+
self._extra_context = {
|
|
185
|
+
"id": self.row.get("id"),
|
|
186
|
+
"proc_inst_id": effective_proc_inst_id,
|
|
187
|
+
"root_proc_inst_id": self.row.get("root_proc_inst_id"),
|
|
188
|
+
"activity_name": self.row.get("activity_name"),
|
|
189
|
+
"agents": agents,
|
|
190
|
+
"users": users,
|
|
191
|
+
"tenant_mcp": tenant_mcp,
|
|
192
|
+
"form_fields": form_fields,
|
|
193
|
+
"form_html": form_html,
|
|
194
|
+
"form_id": form_id,
|
|
195
|
+
"notify_user_emails": notify_emails,
|
|
196
|
+
"summarized_feedback": summarized_feedback,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
logger.info("\n\n🎉 [컨텍스트 준비 완료] 모든 데이터 준비됨")
|
|
131
200
|
|
|
132
201
|
except Exception as e:
|
|
133
|
-
logger.error("❌
|
|
134
|
-
# 사용자 친화 요약은 상위 경계에서 한 번만 기록하도록 넘김
|
|
202
|
+
logger.error("❌ [데이터 조회 실패] %s", str(e))
|
|
135
203
|
raise ContextPreparationError(e)
|
|
136
204
|
|
|
137
|
-
# 3단계: 피드백 요약 처리
|
|
138
|
-
logger.info("📝 피드백 요약 처리 중...")
|
|
139
|
-
feedback_str = self.row.get("feedback", "")
|
|
140
|
-
contents_str = self.row.get("output", "") or self.row.get("draft", "")
|
|
141
|
-
summarized_feedback = ""
|
|
142
|
-
|
|
143
|
-
if feedback_str.strip():
|
|
144
|
-
summarized_feedback = await summarize_feedback(feedback_str, contents_str)
|
|
145
|
-
logger.info("✅ 피드백 요약 완료 - 원본: %d자 → 요약: %d자", len(feedback_str), len(summarized_feedback))
|
|
146
|
-
|
|
147
|
-
# 4단계: 컨텍스트 구성
|
|
148
|
-
logger.info("🏗️ 컨텍스트 구성 중...")
|
|
149
|
-
self._extra_context = {
|
|
150
|
-
"id": self.row.get("id"),
|
|
151
|
-
"proc_inst_id": effective_proc_inst_id,
|
|
152
|
-
"root_proc_inst_id": self.row.get("root_proc_inst_id"),
|
|
153
|
-
"activity_name": self.row.get("activity_name"),
|
|
154
|
-
"agents": agents,
|
|
155
|
-
"tenant_mcp": tenant_mcp,
|
|
156
|
-
"form_fields": form_fields,
|
|
157
|
-
"form_html": form_html,
|
|
158
|
-
"form_id": form_id,
|
|
159
|
-
"notify_user_emails": notify_emails,
|
|
160
|
-
"summarized_feedback": summarized_feedback,
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
logger.info("✅ 컨텍스트 준비 완료! (agents=%d개)",
|
|
164
|
-
len(agents) if isinstance(agents, list) else 0)
|
|
165
|
-
|
|
166
205
|
def get_user_input(self) -> str:
|
|
167
206
|
return self._user_input
|
|
168
207
|
|
|
@@ -197,7 +236,7 @@ class ProcessGPTEventQueue(EventQueue):
|
|
|
197
236
|
try:
|
|
198
237
|
proc_inst_id_val = getattr(event, "contextId", None) or self.proc_inst_id
|
|
199
238
|
todo_id_val = getattr(event, "taskId", None) or str(self.todolist_id)
|
|
200
|
-
logger.info("\n📨 이벤트 수신: %s (task=%s)", type(event).__name__, self.todolist_id)
|
|
239
|
+
logger.info("\n\n📨 이벤트 수신: %s (task=%s)", type(event).__name__, self.todolist_id)
|
|
201
240
|
|
|
202
241
|
# 1) 결과물 저장
|
|
203
242
|
if isinstance(event, TaskArtifactUpdateEvent):
|
|
@@ -348,11 +387,14 @@ class ProcessGPTAgentServer:
|
|
|
348
387
|
|
|
349
388
|
while self.is_running and not self._shutdown_event.is_set():
|
|
350
389
|
try:
|
|
351
|
-
logger.info("🔍
|
|
390
|
+
logger.info("🔍 [폴링 시작] 작업 대기 중... (agent_orch=%s)", self.agent_orch)
|
|
391
|
+
|
|
352
392
|
row = await polling_pending_todos(self.agent_orch, get_consumer_id())
|
|
353
393
|
|
|
354
394
|
if row:
|
|
355
|
-
logger.info("✅ 새
|
|
395
|
+
logger.info("✅ [새 작업 발견] Task ID: %s", row.get("id"))
|
|
396
|
+
logger.info("• Activity: %s | Tool: %s | Tenant: %s",
|
|
397
|
+
row.get("activity_name"), row.get("tool"), row.get("tenant_id"))
|
|
356
398
|
try:
|
|
357
399
|
self._current_todo_id = str(row.get("id"))
|
|
358
400
|
await self.process_todolist_item(row)
|
|
@@ -392,29 +434,21 @@ class ProcessGPTAgentServer:
|
|
|
392
434
|
4) 예외 재전달(상위 루프는 죽지 않고 다음 폴링)
|
|
393
435
|
"""
|
|
394
436
|
task_id = row.get("id")
|
|
395
|
-
logger.info("\n🎯 작업 처리 시작
|
|
396
|
-
logger.info("📝 작업 정보 - proc_inst_id: %s, activity: %s, tool: %s",
|
|
397
|
-
row.get("proc_inst_id"), row.get("activity_name"), row.get("tool"))
|
|
437
|
+
logger.info("\n🎯 [작업 처리 시작] Task ID: %s", task_id)
|
|
398
438
|
|
|
399
439
|
friendly_text: Optional[str] = None
|
|
400
440
|
|
|
401
441
|
try:
|
|
402
442
|
# 1) 컨텍스트 준비 (실패 시 ContextPreparationError로 올라옴)
|
|
403
|
-
logger.info("🔧 컨텍스트 준비 단계 시작...")
|
|
404
443
|
context = ProcessGPTRequestContext(row)
|
|
405
444
|
await context.prepare_context()
|
|
406
|
-
logger.info("✅ 컨텍스트 준비 완료")
|
|
407
445
|
|
|
408
446
|
# 2) 실행
|
|
409
|
-
logger.info("🤖
|
|
447
|
+
logger.info("\n\n🤖 [Agent Orchestrator 실행]")
|
|
410
448
|
event_queue = ProcessGPTEventQueue(str(task_id), self.agent_orch, row.get("proc_inst_id"))
|
|
411
449
|
await self.agent_executor.execute(context, event_queue)
|
|
412
|
-
logger.info("✅ 에이전트 실행 완료")
|
|
413
|
-
|
|
414
|
-
# 3) 정상 완료 이벤트
|
|
415
|
-
logger.info("🏁 작업 완료 처리 중...")
|
|
416
450
|
event_queue.task_done()
|
|
417
|
-
logger.info("🎉
|
|
451
|
+
logger.info("\n\n🎉 [Agent Orchestrator 완료] Task ID: %s", task_id)
|
|
418
452
|
|
|
419
453
|
except Exception as e:
|
|
420
454
|
logger.error("❌ 작업 처리 중 오류 발생: %s", str(e))
|
processgpt_agent_sdk/utils.py
CHANGED
|
@@ -2,15 +2,13 @@ import os
|
|
|
2
2
|
import logging
|
|
3
3
|
import traceback
|
|
4
4
|
from typing import Any, Dict, Optional, List
|
|
5
|
+
from typing import Iterable, Union
|
|
6
|
+
from openai import AsyncOpenAI
|
|
5
7
|
|
|
6
|
-
try:
|
|
7
|
-
# 비동기 클라이언트 사용 → 이벤트 루프 블로킹 방지
|
|
8
|
-
from openai import AsyncOpenAI
|
|
9
|
-
except Exception:
|
|
10
|
-
AsyncOpenAI = None # type: ignore
|
|
11
8
|
|
|
12
9
|
logger = logging.getLogger(__name__)
|
|
13
10
|
|
|
11
|
+
|
|
14
12
|
# ─────────────────────────────
|
|
15
13
|
# Lazy Singleton OpenAI Client
|
|
16
14
|
# ─────────────────────────────
|
|
@@ -119,19 +117,18 @@ async def summarize_error_to_user(exc: Exception, meta: Dict[str, Any]) -> str:
|
|
|
119
117
|
# 폴백 없이 상위 전파
|
|
120
118
|
raise
|
|
121
119
|
|
|
122
|
-
async def summarize_feedback(
|
|
120
|
+
async def summarize_feedback(feedback_data: List[dict], content_data: dict = {}) -> str:
|
|
123
121
|
"""
|
|
124
122
|
피드백과 결과물을 바탕으로 통합된 피드백 요약을 생성.
|
|
125
123
|
- 모델: gpt-4.1-nano (환경변수 FEEDBACK_SUMMARY_MODEL로 재정의 가능)
|
|
126
124
|
- 폴백: 없음 (LLM 실패 시 예외를 상위로 전파)
|
|
127
125
|
"""
|
|
128
126
|
logger.info(
|
|
129
|
-
"🔍 피드백 요약 처리 시작 | 피드백: %
|
|
130
|
-
|
|
131
|
-
)
|
|
127
|
+
"🔍 피드백 요약 처리 시작 | 피드백: %s, 결과물: %s자",
|
|
128
|
+
feedback_data, content_data)
|
|
132
129
|
|
|
133
130
|
system_prompt = _get_feedback_system_prompt()
|
|
134
|
-
user_prompt = _create_feedback_summary_prompt(
|
|
131
|
+
user_prompt = _create_feedback_summary_prompt(feedback_data, content_data)
|
|
135
132
|
|
|
136
133
|
try:
|
|
137
134
|
text = await _llm_request(system_prompt, user_prompt, "FEEDBACK_SUMMARY_MODEL", "gpt-4.1-nano")
|
|
@@ -145,13 +142,13 @@ async def summarize_feedback(feedback_str: str, contents_str: str = "") -> str:
|
|
|
145
142
|
# ─────────────────────────────
|
|
146
143
|
# 프롬프트 유틸
|
|
147
144
|
# ─────────────────────────────
|
|
148
|
-
def _create_feedback_summary_prompt(
|
|
145
|
+
def _create_feedback_summary_prompt(feedback_data: List[dict], content_data: dict = {}) -> str:
|
|
149
146
|
"""피드백 정리 프롬프트 - 현재 결과물과 피드백을 함께 분석"""
|
|
150
147
|
blocks: List[str] = ["다음은 사용자의 피드백과 결과물입니다. 이를 분석하여 통합된 피드백을 작성해주세요:"]
|
|
151
|
-
if
|
|
152
|
-
blocks.append(f"=== 피드백 내용 ===\n{
|
|
153
|
-
if
|
|
154
|
-
blocks.append(f"=== 현재 결과물/작업 내용 ===\n{
|
|
148
|
+
if feedback_data:
|
|
149
|
+
blocks.append(f"=== 피드백 내용 ===\n{feedback_data}")
|
|
150
|
+
if content_data:
|
|
151
|
+
blocks.append(f"=== 현재 결과물/작업 내용 ===\n{content_data}")
|
|
155
152
|
|
|
156
153
|
blocks.append(
|
|
157
154
|
"""**상황 분석 및 처리 방식:**
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
processgpt_agent_sdk/__init__.py,sha256=pn11TgNq6H6zptUwBsO_MNXCQmHTtzot_GwJxR9xSuI,581
|
|
2
|
-
processgpt_agent_sdk/database.py,sha256=i2tgGWe_F8razJho40NEKr3gJXut9AA7uLMsgZY5oJc,9568
|
|
3
|
-
processgpt_agent_sdk/processgpt_agent_framework.py,sha256=M1fhbyUyB6LRzcB8LWq8WA83nYIxsO7GhEOpHWvEU18,21521
|
|
4
|
-
processgpt_agent_sdk/utils.py,sha256=E20OqnuN-HX6m7IaQWAPTwJx-7jFL-otflotHXYvvFU,8598
|
|
5
|
-
process_gpt_agent_sdk-0.3.18.dist-info/METADATA,sha256=pWB3dj1Dsm1Gd5YJv6fS6CCvybwA84lrt31sjfWuUag,21981
|
|
6
|
-
process_gpt_agent_sdk-0.3.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
process_gpt_agent_sdk-0.3.18.dist-info/top_level.txt,sha256=Xe6zrj3_3Vv7d0pl5RRtenVUckwOVBVLQn2P03j5REo,21
|
|
8
|
-
process_gpt_agent_sdk-0.3.18.dist-info/RECORD,,
|
|
File without changes
|
{process_gpt_agent_sdk-0.3.18.dist-info → process_gpt_agent_sdk-0.3.20.dist-info}/top_level.txt
RENAMED
|
File without changes
|