process-gpt-agent-sdk 0.3.18__tar.gz → 0.3.19__tar.gz
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 → process_gpt_agent_sdk-0.3.19}/PKG-INFO +1 -1
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/process_gpt_agent_sdk.egg-info/PKG-INFO +1 -1
- process_gpt_agent_sdk-0.3.19/processgpt_agent_sdk/__init__.py +43 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/processgpt_agent_sdk/database.py +169 -51
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/processgpt_agent_sdk/processgpt_agent_framework.py +109 -65
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/processgpt_agent_sdk/utils.py +12 -15
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/pyproject.toml +1 -1
- process_gpt_agent_sdk-0.3.18/processgpt_agent_sdk/__init__.py +0 -25
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/README.md +0 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/process_gpt_agent_sdk.egg-info/SOURCES.txt +0 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/process_gpt_agent_sdk.egg-info/dependency_links.txt +0 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/process_gpt_agent_sdk.egg-info/requires.txt +0 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/process_gpt_agent_sdk.egg-info/top_level.txt +0 -0
- {process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/setup.cfg +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .processgpt_agent_framework import (
|
|
2
|
+
ProcessGPTAgentServer,
|
|
3
|
+
ProcessGPTRequestContext,
|
|
4
|
+
ProcessGPTEventQueue,
|
|
5
|
+
ContextPreparationError,
|
|
6
|
+
)
|
|
7
|
+
from .database import (
|
|
8
|
+
initialize_db,
|
|
9
|
+
get_consumer_id,
|
|
10
|
+
polling_pending_todos,
|
|
11
|
+
record_event,
|
|
12
|
+
record_events_bulk,
|
|
13
|
+
save_task_result,
|
|
14
|
+
update_task_error,
|
|
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,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ProcessGPTAgentServer",
|
|
27
|
+
"ProcessGPTRequestContext",
|
|
28
|
+
"ProcessGPTEventQueue",
|
|
29
|
+
"ContextPreparationError",
|
|
30
|
+
"initialize_db",
|
|
31
|
+
"get_consumer_id",
|
|
32
|
+
"polling_pending_todos",
|
|
33
|
+
"record_event",
|
|
34
|
+
"record_events_bulk",
|
|
35
|
+
"save_task_result",
|
|
36
|
+
"update_task_error",
|
|
37
|
+
"fetch_form_def",
|
|
38
|
+
"fetch_users_grouped",
|
|
39
|
+
"fetch_email_users_by_proc_inst_id",
|
|
40
|
+
"fetch_tenant_mcp",
|
|
41
|
+
"summarize_error_to_user",
|
|
42
|
+
"summarize_feedback",
|
|
43
|
+
]
|
{process_gpt_agent_sdk-0.3.18 → process_gpt_agent_sdk-0.3.19}/processgpt_agent_sdk/database.py
RENAMED
|
@@ -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,109 @@ 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
|
+
logger.info("-" * 60)
|
|
129
|
+
|
|
130
|
+
# Users 정보
|
|
131
|
+
if users:
|
|
132
|
+
user_info = []
|
|
133
|
+
for u in users[:5]:
|
|
134
|
+
name = u.get("name", u.get("user_name", "Unknown"))
|
|
135
|
+
email = u.get("email", "")
|
|
136
|
+
user_info.append(f"{name}({email})" if email else name)
|
|
137
|
+
logger.info("• Users (%d명): %s%s", len(users), ", ".join(user_info), "..." if len(users) > 5 else "")
|
|
138
|
+
else:
|
|
139
|
+
logger.info("• Users: 없음")
|
|
140
|
+
|
|
141
|
+
# Agents 정보
|
|
142
|
+
if agents:
|
|
143
|
+
agent_info = []
|
|
144
|
+
for a in agents[:5]:
|
|
145
|
+
name = a.get("name", a.get("agent_name", "Unknown"))
|
|
146
|
+
tools = a.get("tools", [])
|
|
147
|
+
tool_names = [t.get("name", str(t)) for t in tools[:3]] if tools else []
|
|
148
|
+
tool_str = f"[{', '.join(tool_names)}]" if tool_names else ""
|
|
149
|
+
agent_info.append(f"{name}{tool_str}")
|
|
150
|
+
logger.info("• Agents (%d개): %s%s", len(agents), ", ".join(agent_info), "..." if len(agents) > 5 else "")
|
|
151
|
+
else:
|
|
152
|
+
logger.info("• Agents: 없음")
|
|
153
|
+
|
|
154
|
+
# Form 정보
|
|
155
|
+
if form_fields:
|
|
156
|
+
pretty_json = json.dumps(form_fields, ensure_ascii=False, separators=(',', ':'))
|
|
157
|
+
logger.info("• Form: %s (%d개 필드) - %s", form_id, len(form_fields), pretty_json)
|
|
158
|
+
else:
|
|
159
|
+
logger.info("• Form: %s (필드 없음)", form_id)
|
|
126
160
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
161
|
+
# Notify 정보
|
|
162
|
+
if notify_emails:
|
|
163
|
+
email_list = notify_emails.split(',') if ',' in notify_emails else [notify_emails]
|
|
164
|
+
logger.info("• Notify (%d개): %s", len(email_list),
|
|
165
|
+
", ".join(email_list[:3]) + ("..." if len(email_list) > 3 else ""))
|
|
166
|
+
else:
|
|
167
|
+
logger.info("• Notify: 없음")
|
|
168
|
+
|
|
169
|
+
# MCP 정보 - 상세 표시
|
|
170
|
+
if tenant_mcp:
|
|
171
|
+
logger.info("• %s 테넌트에 연결된 MCP 설정 정보가 존재합니다.", tenant_id)
|
|
172
|
+
else:
|
|
173
|
+
logger.info("• %s 테넌트에 연결된 MCP 설정 정보가 존재하지 않습니다.", tenant_id)
|
|
174
|
+
|
|
175
|
+
# 피드백 처리
|
|
176
|
+
feedback_data = self.row.get("feedback")
|
|
177
|
+
content_data = self.row.get("output") or self.row.get("draft")
|
|
178
|
+
summarized_feedback = ""
|
|
179
|
+
if feedback_data:
|
|
180
|
+
logger.info("\n\n📝 [피드백 처리]")
|
|
181
|
+
logger.info("-" * 60)
|
|
182
|
+
logger.info("• %d자 → AI 요약 중...", len(feedback_data))
|
|
183
|
+
summarized_feedback = await summarize_feedback(feedback_data, content_data)
|
|
184
|
+
logger.info("• 요약 완료: %d자", len(summarized_feedback))
|
|
185
|
+
|
|
186
|
+
# 컨텍스트 구성
|
|
187
|
+
self._extra_context = {
|
|
188
|
+
"id": self.row.get("id"),
|
|
189
|
+
"proc_inst_id": effective_proc_inst_id,
|
|
190
|
+
"root_proc_inst_id": self.row.get("root_proc_inst_id"),
|
|
191
|
+
"activity_name": self.row.get("activity_name"),
|
|
192
|
+
"agents": agents,
|
|
193
|
+
"users": users,
|
|
194
|
+
"tenant_mcp": tenant_mcp,
|
|
195
|
+
"form_fields": form_fields,
|
|
196
|
+
"form_html": form_html,
|
|
197
|
+
"form_id": form_id,
|
|
198
|
+
"notify_user_emails": notify_emails,
|
|
199
|
+
"summarized_feedback": summarized_feedback,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
logger.info("\n\n🎉 [컨텍스트 준비 완료] 모든 데이터 준비됨")
|
|
203
|
+
logger.info("-"*60)
|
|
131
204
|
|
|
132
205
|
except Exception as e:
|
|
133
|
-
logger.error("❌
|
|
134
|
-
# 사용자 친화 요약은 상위 경계에서 한 번만 기록하도록 넘김
|
|
206
|
+
logger.error("❌ [데이터 조회 실패] %s", str(e))
|
|
135
207
|
raise ContextPreparationError(e)
|
|
136
208
|
|
|
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
209
|
def get_user_input(self) -> str:
|
|
167
210
|
return self._user_input
|
|
168
211
|
|
|
@@ -348,11 +391,18 @@ class ProcessGPTAgentServer:
|
|
|
348
391
|
|
|
349
392
|
while self.is_running and not self._shutdown_event.is_set():
|
|
350
393
|
try:
|
|
351
|
-
logger.info("
|
|
394
|
+
logger.info("\n\n" + "-"*80)
|
|
395
|
+
logger.info("🔍 [폴링 시작] 작업 대기 중... (agent_orch=%s)", self.agent_orch)
|
|
396
|
+
logger.info("-"*80)
|
|
397
|
+
|
|
352
398
|
row = await polling_pending_todos(self.agent_orch, get_consumer_id())
|
|
353
399
|
|
|
354
400
|
if row:
|
|
355
|
-
logger.info("
|
|
401
|
+
logger.info("\n\n" + "-"*80)
|
|
402
|
+
logger.info("✅ [새 작업 발견] Task ID: %s", row.get("id"))
|
|
403
|
+
logger.info("• Activity: %s | Tool: %s | Tenant: %s",
|
|
404
|
+
row.get("activity_name"), row.get("tool"), row.get("tenant_id"))
|
|
405
|
+
logger.info("-"*80)
|
|
356
406
|
try:
|
|
357
407
|
self._current_todo_id = str(row.get("id"))
|
|
358
408
|
await self.process_todolist_item(row)
|
|
@@ -392,29 +442,23 @@ class ProcessGPTAgentServer:
|
|
|
392
442
|
4) 예외 재전달(상위 루프는 죽지 않고 다음 폴링)
|
|
393
443
|
"""
|
|
394
444
|
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"))
|
|
445
|
+
logger.info("\n🎯 [작업 처리 시작] Task ID: %s", task_id)
|
|
398
446
|
|
|
399
447
|
friendly_text: Optional[str] = None
|
|
400
448
|
|
|
401
449
|
try:
|
|
402
450
|
# 1) 컨텍스트 준비 (실패 시 ContextPreparationError로 올라옴)
|
|
403
|
-
logger.info("🔧 컨텍스트 준비 단계 시작...")
|
|
404
451
|
context = ProcessGPTRequestContext(row)
|
|
405
452
|
await context.prepare_context()
|
|
406
|
-
logger.info("✅ 컨텍스트 준비 완료")
|
|
407
453
|
|
|
408
454
|
# 2) 실행
|
|
409
|
-
logger.info("🤖
|
|
455
|
+
logger.info("\n\n🤖 [Agent Orchestrator 실행]")
|
|
456
|
+
logger.info("-" * 60)
|
|
410
457
|
event_queue = ProcessGPTEventQueue(str(task_id), self.agent_orch, row.get("proc_inst_id"))
|
|
411
458
|
await self.agent_executor.execute(context, event_queue)
|
|
412
|
-
logger.info("✅ 에이전트 실행 완료")
|
|
413
|
-
|
|
414
|
-
# 3) 정상 완료 이벤트
|
|
415
|
-
logger.info("🏁 작업 완료 처리 중...")
|
|
416
459
|
event_queue.task_done()
|
|
417
|
-
logger.info("🎉
|
|
460
|
+
logger.info("\n🎉 [Agent Orchestrator 완료] Task ID: %s", task_id)
|
|
461
|
+
logger.info("-"*60)
|
|
418
462
|
|
|
419
463
|
except Exception as e:
|
|
420
464
|
logger.error("❌ 작업 처리 중 오류 발생: %s", str(e))
|
|
@@ -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,25 +0,0 @@
|
|
|
1
|
-
from .processgpt_agent_framework import ProcessGPTAgentServer
|
|
2
|
-
from .database import (
|
|
3
|
-
initialize_db,
|
|
4
|
-
get_consumer_id,
|
|
5
|
-
polling_pending_todos,
|
|
6
|
-
record_event,
|
|
7
|
-
record_events_bulk,
|
|
8
|
-
save_task_result,
|
|
9
|
-
update_task_error,
|
|
10
|
-
fetch_context_bundle,
|
|
11
|
-
)
|
|
12
|
-
from .utils import summarize_error_to_user
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"ProcessGPTAgentServer",
|
|
16
|
-
"initialize_db",
|
|
17
|
-
"get_consumer_id",
|
|
18
|
-
"polling_pending_todos",
|
|
19
|
-
"record_event",
|
|
20
|
-
"record_events_bulk",
|
|
21
|
-
"save_task_result",
|
|
22
|
-
"update_task_error",
|
|
23
|
-
"fetch_context_bundle",
|
|
24
|
-
"summarize_error_to_user",
|
|
25
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|