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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: process-gpt-agent-sdk
3
- Version: 0.3.18
3
+ Version: 0.3.19
4
4
  Summary: Supabase 기반 이벤트/작업 폴링으로 A2A AgentExecutor를 실행하는 SDK
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/your-org/process-gpt-agent-sdk
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: process-gpt-agent-sdk
3
- Version: 0.3.18
3
+ Version: 0.3.19
4
4
  Summary: Supabase 기반 이벤트/작업 폴링으로 A2A AgentExecutor를 실행하는 SDK
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/your-org/process-gpt-agent-sdk
@@ -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
+ ]
@@ -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
- return rows[0] if rows else None
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
- fetch_context_bundle,
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
- # 2단계: 컨텍스트 번들 조회
121
- logger.info("🔍 컨텍스트 번들 조회 중...")
122
- notify_emails, tenant_mcp, form_tuple, agents = await fetch_context_bundle(
123
- effective_proc_inst_id, tenant_id, tool_val, user_ids
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
- logger.info("📦 컨텍스트 번들 조회 완료 - agents: %d개, notify_emails: %s, form_type: %s",
128
- len(agents) if isinstance(agents, list) else 0,
129
- "있음" if notify_emails else "없음",
130
- "자유형식" if form_id == "freeform" else "정의된 폼")
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("❌ 컨텍스트 번들 조회 실패: %s", str(e))
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("🔍 Polling for tasks (agent_orch=%s)...", self.agent_orch)
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("✅ 새 작업: %s (proc=%s, activity=%s)", row.get("id"), row.get("proc_inst_id"), row.get("activity_name"))
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🎯 작업 처리 시작 - Task ID: %s", task_id)
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("🎉 작업 완료: %s\n", task_id)
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(feedback_str: str, contents_str: str = "") -> str:
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
- "🔍 피드백 요약 처리 시작 | 피드백: %d자, 결과물: %d자",
130
- len(feedback_str or ""), len(contents_str or "")
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(feedback_str, contents_str)
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(feedbacks_str: str, contents_str: str = "") -> str:
145
+ def _create_feedback_summary_prompt(feedback_data: List[dict], content_data: dict = {}) -> str:
149
146
  """피드백 정리 프롬프트 - 현재 결과물과 피드백을 함께 분석"""
150
147
  blocks: List[str] = ["다음은 사용자의 피드백과 결과물입니다. 이를 분석하여 통합된 피드백을 작성해주세요:"]
151
- if feedbacks_str and feedbacks_str.strip():
152
- blocks.append(f"=== 피드백 내용 ===\n{feedbacks_str}")
153
- if contents_str and contents_str.strip():
154
- blocks.append(f"=== 현재 결과물/작업 내용 ===\n{contents_str}")
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
  """**상황 분석 및 처리 방식:**
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "process-gpt-agent-sdk"
7
- version = "0.3.18"
7
+ version = "0.3.19"
8
8
  description = "Supabase 기반 이벤트/작업 폴링으로 A2A AgentExecutor를 실행하는 SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -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
- ]