process-gpt-agent-sdk 0.3.12__py3-none-any.whl → 0.3.13__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.

@@ -0,0 +1,690 @@
1
+ Metadata-Version: 2.4
2
+ Name: process-gpt-agent-sdk
3
+ Version: 0.3.13
4
+ Summary: Supabase 기반 이벤트/작업 폴링으로 A2A AgentExecutor를 실행하는 SDK
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/your-org/process-gpt-agent-sdk
7
+ Project-URL: Issues, https://github.com/your-org/process-gpt-agent-sdk/issues
8
+ Keywords: agent,a2a,supabase,workflow,sdk,processgpt
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: supabase>=2.0.0
16
+ Requires-Dist: python-dotenv>=1.0.0
17
+ Requires-Dist: click>=8.0.0
18
+ Requires-Dist: asyncio-mqtt>=0.13.0
19
+ Requires-Dist: jsonschema>=4.0.0
20
+ Requires-Dist: structlog>=23.0.0
21
+ Requires-Dist: typing-extensions>=4.0.0
22
+ Requires-Dist: python-dateutil>=2.8.0
23
+ Requires-Dist: a2a-sdk==0.3.0
24
+
25
+ # ProcessGPT Agent Framework
26
+ ## A2A SDK 연동을 위한 경량 에이전트 서버 프레임워크
27
+
28
+ Supabase 기반의 프로세스 작업(Todolist)을 폴링하고, A2A 규격 이벤트를 통해 작업 상태/결과를 기록하는 **경량 에이전트 서버 프레임워크**입니다.
29
+
30
+ ### 📋 요구사항
31
+ - **런타임**: Python 3.9+ (권장: Python 3.11)
32
+ - **데이터베이스**: Supabase (PostgreSQL) + 제공된 RPC/테이블
33
+ - **이벤트 규격**: A2A `TaskStatusUpdateEvent` / `TaskArtifactUpdateEvent`
34
+
35
+ ## 📊 이벤트 종류 및 데이터 구조
36
+
37
+ ### 🎯 이벤트 타입 (event_type) 종류
38
+
39
+ | event_type | 설명 | 사용 시점 | 자동 설정 여부 |
40
+ |------------|------|-----------|----------------|
41
+ | `task_started` | 작업 시작 | 작업 처리 시작시 | 수동 설정 |
42
+ | `task_completed` | 작업 완료 | 작업 정상 완료시 | 수동 설정 |
43
+ | `crew_completed` | 크루 작업 완료 | 서버가 작업 종료시 | **자동 설정** |
44
+ | `tool_usage_started` | 도구 사용 시작 | 외부 도구/API 호출 시작 | 수동 설정 |
45
+ | `tool_usage_finished` | 도구 사용 완료 | 외부 도구/API 호출 완료 | 수동 설정 |
46
+ | `human_asked` | 사용자 입력 요청 | HITL 패턴 사용시 | **자동 설정** |
47
+ | `human_response` | 사용자 응답 | UI에서 사용자 응답시 | UI가 설정 |
48
+
49
+ ### 📋 메타데이터 필드 설명
50
+
51
+ #### crew_type (필수)
52
+ - **의미**: 현재 조직의 크루 이름 또는 행위를 나타냄
53
+ - **예시**: `action`, `report`, `slide`, `analysis`, `research` 등
54
+ - **사용법**: 어떤 종류의 작업인지 분류하는 데 사용
55
+
56
+ ```python
57
+ metadata = {
58
+ "crew_type": "action", # 액션 수행 크루
59
+ "event_type": "task_started"
60
+ }
61
+ ```
62
+
63
+ #### job_id (중요)
64
+ - **의미**: 하나의 작업 단위를 식별하는 고유 ID
65
+ - **규칙**: **시작과 끝이 반드시 매칭되어야 함**
66
+ - **형식**: `job-{task_id}` 또는 `job-{timestamp}` 등
67
+
68
+ ```python
69
+ # 올바른 사용법 - 동일한 job_id 사용
70
+ job_id = f"job-{task_id}"
71
+
72
+ # 작업 시작
73
+ metadata = {"crew_type": "action", "event_type": "task_started", "job_id": job_id}
74
+
75
+ # HITL 요청
76
+ metadata = {"crew_type": "action", "job_id": job_id} # human_asked 자동 설정
77
+
78
+ # 작업 완료
79
+ metadata = {"crew_type": "action", "event_type": "task_completed", "job_id": job_id}
80
+ ```
81
+
82
+ ### 🔄 이벤트 저장 방식
83
+
84
+ #### 1. TaskStatusUpdateEvent → `events` 테이블
85
+ ```python
86
+ event_queue.enqueue_event(
87
+ TaskStatusUpdateEvent(
88
+ status={
89
+ "state": TaskState.working,
90
+ "message": new_agent_text_message("진행 상황 메시지", context_id, task_id),
91
+ },
92
+ final=False,
93
+ contextId=context_id,
94
+ taskId=task_id,
95
+ metadata={
96
+ "crew_type": "action",
97
+ "event_type": "task_started", # events.event_type에 저장
98
+ "job_id": "job-12345"
99
+ }
100
+ )
101
+ )
102
+ ```
103
+
104
+ **저장 결과** (events 테이블):
105
+ - `event_type`: "task_started"
106
+ - `data`: "진행 상황 메시지" (래퍼 제거된 순수 텍스트)
107
+ - `metadata`: 전체 metadata JSON
108
+
109
+ #### 2. TaskArtifactUpdateEvent → `todolist.output` 컬럼
110
+ ```python
111
+ artifact = new_text_artifact(
112
+ name="처리결과",
113
+ description="작업 완료 결과",
114
+ text=json.dumps({"result": "완료"}, ensure_ascii=False)
115
+ )
116
+ event_queue.enqueue_event(
117
+ TaskArtifactUpdateEvent(
118
+ artifact=artifact,
119
+ lastChunk=True, # 최종 결과
120
+ contextId=context_id,
121
+ taskId=task_id,
122
+ )
123
+ )
124
+ ```
125
+
126
+ **저장 결과** (todolist 테이블):
127
+ - `output`: `{"result": "완료"}` (래퍼 제거된 순수 JSON)
128
+ - `p_final`: `true`
129
+
130
+ ### ⚠️ 특별 규칙
131
+
132
+ 1. **자동 event_type 설정**:
133
+ - `state=input_required` → `event_type=human_asked` (자동)
134
+ - 작업 완료시 → `event_type=crew_completed` (서버가 자동 추가)
135
+
136
+ 2. **JSON 문자열 변환 필수**:
137
+ ```python
138
+ # 올바른 방법
139
+ text=json.dumps(data, ensure_ascii=False)
140
+
141
+ # 잘못된 방법
142
+ text=data # 딕셔너리 직접 전달 시 래퍼와 함께 저장됨
143
+ ```
144
+
145
+ ## 🔄 전체 데이터 흐름도
146
+
147
+ ```mermaid
148
+ graph TD
149
+ A[Supabase todolist 테이블] -->|폴링| B[ProcessGPTAgentServer]
150
+ B -->|작업 발견| C[RequestContext 생성]
151
+ C -->|컨텍스트 전달| D[AgentExecutor.execute]
152
+
153
+ D -->|진행 상태| E[TaskStatusUpdateEvent]
154
+ D -->|최종 결과| F[TaskArtifactUpdateEvent]
155
+ D -->|HITL 요청| G[TaskStatusUpdateEvent<br/>state=input_required]
156
+
157
+ E -->|래퍼 제거| H[events 테이블<br/>data 컬럼]
158
+ F -->|래퍼 제거| I[todolist.output 컬럼<br/>p_final=true]
159
+ G -->|자동 설정| J[events 테이블<br/>event_type=human_asked]
160
+
161
+ K[Operator UI] -->|사용자 응답| L[events 테이블<br/>event_type=human_response]
162
+
163
+ style E fill:#e1f5fe
164
+ style F fill:#f3e5f5
165
+ style G fill:#fff3e0
166
+ ```
167
+
168
+ ### 🎯 job_id 생명주기 흐름
169
+
170
+ ```mermaid
171
+ sequenceDiagram
172
+ participant S as Server
173
+ participant E as Executor
174
+ participant DB as Supabase
175
+ participant UI as Operator UI
176
+
177
+ Note over E: job_id = "job-12345" 생성
178
+
179
+ E->>DB: TaskStatusUpdateEvent<br/>event_type=task_started<br/>job_id=job-12345
180
+
181
+ E->>DB: TaskStatusUpdateEvent<br/>event_type=tool_usage_started<br/>job_id=job-12345
182
+
183
+ E->>DB: TaskStatusUpdateEvent<br/>event_type=tool_usage_finished<br/>job_id=job-12345
184
+
185
+ alt HITL 필요시
186
+ E->>DB: TaskStatusUpdateEvent<br/>state=input_required<br/>job_id=job-12345
187
+ Note over DB: 자동으로 event_type=human_asked 설정
188
+
189
+ UI->>DB: INSERT events<br/>event_type=human_response<br/>job_id=job-12345
190
+ end
191
+
192
+ E->>DB: TaskArtifactUpdateEvent<br/>lastChunk=true
193
+
194
+ E->>DB: TaskStatusUpdateEvent<br/>event_type=task_completed<br/>job_id=job-12345
195
+
196
+ S->>DB: TaskStatusUpdateEvent<br/>event_type=crew_completed<br/>job_id=job-12345
197
+
198
+ Note over S,UI: 동일한 job_id로 시작부터 끝까지 추적 가능
199
+ ```
200
+
201
+ ### 💾 데이터 저장 구조
202
+
203
+ #### events 테이블 저장 예시
204
+ ```json
205
+ {
206
+ "id": "uuid",
207
+ "event_type": "task_started",
208
+ "data": "작업을 시작합니다", // 래퍼 제거된 순수 메시지
209
+ "metadata": {
210
+ "crew_type": "action",
211
+ "event_type": "task_started",
212
+ "job_id": "job-12345",
213
+ "contextId": "proc-789",
214
+ "taskId": "task-456"
215
+ },
216
+ "created_at": "2024-01-01T00:00:00Z"
217
+ }
218
+ ```
219
+
220
+ #### todolist.output 저장 예시
221
+ ```json
222
+ {
223
+ "id": "task-456",
224
+ "output": {
225
+ "status": "completed",
226
+ "result": "처리 결과 데이터"
227
+ }, // 래퍼 제거된 순수 아티팩트 데이터
228
+ "p_final": true,
229
+ "updated_at": "2024-01-01T00:05:00Z"
230
+ }
231
+ ```
232
+
233
+ ### 🔧 값 전달 과정
234
+ ```python
235
+ # 1. 서버에서 작업 정보 가져오기
236
+ row = context.get_context_data()["row"] # todolist 테이블의 한 행
237
+ context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id") # 프로세스 ID
238
+ task_id = row.get("id") # 작업 ID
239
+ user_input = context.get_user_input() # 사용자가 입력한 내용
240
+
241
+ # 2. job_id 생성 (작업 전체 추적용)
242
+ job_id = f"job-{task_id}" # 또는 timestamp 기반
243
+
244
+ # 3. 메시지/아티팩트 생성시 JSON 문자열로 변환
245
+ payload = {"result": "처리 완료"}
246
+ message_text = json.dumps(payload, ensure_ascii=False) # 중요: JSON 문자열로!
247
+
248
+ # 4. 메타데이터에 crew_type, job_id 포함
249
+ metadata = {
250
+ "crew_type": "action", # 크루 타입
251
+ "event_type": "task_started", # 이벤트 타입
252
+ "job_id": job_id # 작업 추적 ID
253
+ }
254
+
255
+ # 5. 서버가 자동으로 래퍼 제거 후 순수 payload만 저장
256
+ # events.data 또는 todolist.output에 {"result": "처리 완료"}만 저장됨
257
+ ```
258
+
259
+ ## 🚀 빠른 시작 가이드
260
+
261
+ ### 1단계: 설치
262
+ ```bash
263
+ # 패키지 설치
264
+ pip install -e .
265
+
266
+ # 또는 requirements.txt 사용
267
+ pip install -r requirements.txt
268
+ ```
269
+
270
+ ### 2단계: 환경 설정
271
+ `.env` 파일 생성:
272
+ ```env
273
+ SUPABASE_URL=your_supabase_project_url
274
+ SUPABASE_KEY=your_supabase_anon_key
275
+ ENV=dev
276
+ ```
277
+
278
+ ### 3단계: 서버 구현 방법
279
+ 서버는 이렇게 만드세요:
280
+
281
+ ```python
282
+ # my_server.py
283
+ import asyncio
284
+ from dotenv import load_dotenv
285
+ from processgpt_agent_sdk.processgpt_agent_framework import ProcessGPTAgentServer
286
+ from my_executor import MyExecutor # 아래에서 구현할 익스큐터
287
+
288
+ async def main():
289
+ load_dotenv()
290
+
291
+ server = ProcessGPTAgentServer(
292
+ agent_executor=MyExecutor(), # 여러분이 구현할 익스큐터
293
+ agent_type="my-agent" # Supabase todolist.agent_orch와 매칭되어야 함
294
+ )
295
+ server.polling_interval = 3 # 3초마다 새 작업 확인
296
+
297
+ print("서버 시작!")
298
+ await server.run()
299
+
300
+ if __name__ == "__main__":
301
+ try:
302
+ asyncio.run(main())
303
+ except KeyboardInterrupt:
304
+ print("서버 종료")
305
+ ```
306
+
307
+ ### 4단계: 익스큐터 구현 방법
308
+ 익스큐터는 이렇게 만드세요:
309
+
310
+ ```python
311
+ # my_executor.py
312
+ import asyncio
313
+ import json
314
+ from typing_extensions import override
315
+ from a2a.server.agent_execution import AgentExecutor, RequestContext
316
+ from a2a.server.events import EventQueue
317
+ from a2a.types import TaskStatusUpdateEvent, TaskState, TaskArtifactUpdateEvent
318
+ from a2a.utils import new_agent_text_message, new_text_artifact
319
+
320
+ class MyExecutor(AgentExecutor):
321
+ @override
322
+ async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
323
+ # 1. 작업 정보 가져오기
324
+ row = context.get_context_data()["row"]
325
+ context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id")
326
+ task_id = row.get("id")
327
+ user_input = context.get_user_input() # 사용자가 입력한 내용
328
+
329
+ # 2. job_id 생성 (작업 전체 추적용)
330
+ job_id = f"job-{task_id}"
331
+
332
+ print(f"처리할 작업: {user_input} (job_id: {job_id})")
333
+
334
+ # 3. 작업 시작 알림 (events 테이블에 저장됨)
335
+ event_queue.enqueue_event(
336
+ TaskStatusUpdateEvent(
337
+ status={
338
+ "state": TaskState.working,
339
+ "message": new_agent_text_message("작업 시작", context_id, task_id),
340
+ },
341
+ final=False,
342
+ contextId=context_id,
343
+ taskId=task_id,
344
+ metadata={
345
+ "crew_type": "action", # 크루 타입
346
+ "event_type": "task_started",
347
+ "job_id": job_id # 작업 추적 ID
348
+ }
349
+ )
350
+ )
351
+
352
+ # 4. 실제 작업 수행 (여기에 여러분의 로직 작성)
353
+ await asyncio.sleep(2)
354
+ result_data = {"status": "완료", "input": user_input, "output": "처리 결과"}
355
+
356
+ # 5. 작업 완료 알림
357
+ event_queue.enqueue_event(
358
+ TaskStatusUpdateEvent(
359
+ status={
360
+ "state": TaskState.working,
361
+ "message": new_agent_text_message("작업 완료", context_id, task_id),
362
+ },
363
+ final=False,
364
+ contextId=context_id,
365
+ taskId=task_id,
366
+ metadata={
367
+ "crew_type": "action",
368
+ "event_type": "task_completed",
369
+ "job_id": job_id # 동일한 job_id 사용
370
+ }
371
+ )
372
+ )
373
+
374
+ # 6. 최종 결과 전송 (todolist.output에 저장됨)
375
+ artifact = new_text_artifact(
376
+ name="처리결과",
377
+ description="작업 완료 결과",
378
+ text=json.dumps(result_data, ensure_ascii=False) # JSON 문자열로!
379
+ )
380
+ event_queue.enqueue_event(
381
+ TaskArtifactUpdateEvent(
382
+ artifact=artifact,
383
+ lastChunk=True, # 중요: 최종 결과면 True
384
+ contextId=context_id,
385
+ taskId=task_id,
386
+ )
387
+ )
388
+
389
+ @override
390
+ async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
391
+ pass # 취소 로직 (필요시 구현)
392
+ ```
393
+
394
+ ### 5단계: 실행
395
+ ```bash
396
+ python my_server.py
397
+ ```
398
+
399
+ ## 🤝 Human-in-the-Loop (사용자 입력 요청) 패턴
400
+
401
+ 사용자 입력이 필요한 완전한 예시:
402
+
403
+ ```python
404
+ class HITLExecutor(AgentExecutor):
405
+ @override
406
+ async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
407
+ row = context.get_context_data()["row"]
408
+ context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id")
409
+ task_id = row.get("id")
410
+ user_input = context.get_user_input()
411
+ job_id = f"job-{task_id}"
412
+
413
+ # 1. 작업 시작
414
+ event_queue.enqueue_event(
415
+ TaskStatusUpdateEvent(
416
+ status={
417
+ "state": TaskState.working,
418
+ "message": new_agent_text_message("분석을 시작합니다", context_id, task_id),
419
+ },
420
+ final=False,
421
+ contextId=context_id,
422
+ taskId=task_id,
423
+ metadata={
424
+ "crew_type": "analysis", # 분석 크루
425
+ "event_type": "task_started",
426
+ "job_id": job_id
427
+ }
428
+ )
429
+ )
430
+
431
+ await asyncio.sleep(1)
432
+
433
+ # 2. 사용자 입력 요청 (HITL)
434
+ question_data = {
435
+ "question": f"'{user_input}' 작업을 어떤 방식으로 처리할까요?",
436
+ "options": ["빠른 처리", "정밀 분석", "단계별 진행"],
437
+ "context": user_input
438
+ }
439
+
440
+ event_queue.enqueue_event(
441
+ TaskStatusUpdateEvent(
442
+ status={
443
+ "state": TaskState.input_required, # 중요: 자동으로 human_asked 설정됨
444
+ "message": new_agent_text_message(
445
+ json.dumps(question_data, ensure_ascii=False),
446
+ context_id, task_id
447
+ ),
448
+ },
449
+ final=True,
450
+ contextId=context_id,
451
+ taskId=task_id,
452
+ metadata={
453
+ "crew_type": "analysis",
454
+ "job_id": job_id # 동일한 job_id 유지
455
+ }
456
+ )
457
+ )
458
+
459
+ # 3. 사용자 응답을 기다리는 로직 (실제 구현에서는 필요)
460
+ # 여기서는 시뮬레이션
461
+ await asyncio.sleep(3)
462
+
463
+ # 4. 사용자 응답 후 작업 완료
464
+ result_data = {
465
+ "original_request": user_input,
466
+ "user_choice": "사용자가 선택한 옵션",
467
+ "result": "HITL 방식으로 처리 완료"
468
+ }
469
+
470
+ # 5. 완료 알림
471
+ event_queue.enqueue_event(
472
+ TaskStatusUpdateEvent(
473
+ status={
474
+ "state": TaskState.working,
475
+ "message": new_agent_text_message("HITL 처리 완료", context_id, task_id),
476
+ },
477
+ final=False,
478
+ contextId=context_id,
479
+ taskId=task_id,
480
+ metadata={
481
+ "crew_type": "analysis",
482
+ "event_type": "task_completed",
483
+ "job_id": job_id # 동일한 job_id로 완료
484
+ }
485
+ )
486
+ )
487
+
488
+ # 6. 최종 결과
489
+ artifact = new_text_artifact(
490
+ name="HITL_결과",
491
+ description="Human-in-the-Loop 처리 결과",
492
+ text=json.dumps(result_data, ensure_ascii=False)
493
+ )
494
+ event_queue.enqueue_event(
495
+ TaskArtifactUpdateEvent(
496
+ artifact=artifact,
497
+ lastChunk=True,
498
+ contextId=context_id,
499
+ taskId=task_id,
500
+ )
501
+ )
502
+
503
+ @override
504
+ async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
505
+ pass
506
+ ```
507
+
508
+ ## 📋 체크리스트 (실패 없는 통합을 위한)
509
+
510
+ ### 필수 설정
511
+ - [ ] `.env`에 `SUPABASE_URL`, `SUPABASE_KEY` 설정
512
+ - [ ] `requirements.txt` 설치 완료
513
+ - [ ] Supabase에서 제공 SQL(`database_schema.sql`, `function.sql`) 적용
514
+
515
+ ### 코드 구현
516
+ - [ ] 서버에서 `agent_type`이 Supabase `todolist.agent_orch`와 매칭됨
517
+ - [ ] 익스큐터에서 `contextId`, `taskId`를 올바르게 설정
518
+ - [ ] **job_id 생성 및 일관성 유지** (`job-{task_id}` 형식 권장)
519
+ - [ ] **crew_type 설정** (`action`, `report`, `slide` 등 행위별 분류)
520
+ - [ ] 상태 이벤트는 `new_agent_text_message()`로 생성
521
+ - [ ] 최종 결과는 `new_text_artifact()` + `lastChunk=True`로 전송
522
+ - [ ] HITL 요청시 `TaskState.input_required` 사용
523
+ - [ ] **JSON 문자열 변환** (`json.dumps(data, ensure_ascii=False)`)
524
+
525
+ ## 🚨 자주 발생하는 문제
526
+
527
+ ### 1. 설치 문제
528
+ **증상**: `ModuleNotFoundError`
529
+ ```bash
530
+ # 해결
531
+ pip install -e .
532
+ pip install a2a-sdk==0.3.0 --force-reinstall
533
+ ```
534
+
535
+ ### 2. 작업이 폴링되지 않음
536
+ **원인**: Supabase 연결 문제
537
+ **해결**:
538
+ - `.env` 파일 위치 확인 (프로젝트 루트)
539
+ - URL/Key 재확인
540
+ - `agent_type`이 todolist.agent_orch와 매칭되는지 확인
541
+
542
+ ### 3. 이벤트가 저장되지 않음
543
+ **원인**: 테이블/함수 누락
544
+ **해결**:
545
+ - `database_schema.sql`, `function.sql` 실행 확인
546
+ - Supabase 테이블 권한 확인
547
+
548
+ ### 4. 결과가 래퍼와 함께 저장됨
549
+ **원인**: JSON 문자열 변환 누락
550
+ ```python
551
+ # 올바른 방법
552
+ text=json.dumps(data, ensure_ascii=False) # JSON 문자열로!
553
+
554
+ # 잘못된 방법
555
+ text=data # 딕셔너리 직접 전달 (X)
556
+ ```
557
+
558
+ ## 📚 샘플 코드 (간단 버전)
559
+
560
+ ### 기본 서버
561
+ ```python
562
+ # sample_server/minimal_server.py
563
+ import asyncio
564
+ from dotenv import load_dotenv
565
+ from processgpt_agent_sdk.processgpt_agent_framework import ProcessGPTAgentServer
566
+ from sample_server.minimal_executor import MinimalExecutor
567
+
568
+ async def main():
569
+ load_dotenv()
570
+ server = ProcessGPTAgentServer(
571
+ agent_executor=MinimalExecutor(),
572
+ agent_type="crewai-action"
573
+ )
574
+ server.polling_interval = 3
575
+ await server.run()
576
+
577
+ if __name__ == "__main__":
578
+ try:
579
+ asyncio.run(main())
580
+ except KeyboardInterrupt:
581
+ pass
582
+ ```
583
+
584
+ ### 기본 익스큐터
585
+ ```python
586
+ # sample_server/minimal_executor.py
587
+ import asyncio
588
+ import json
589
+ from typing_extensions import override
590
+ from a2a.server.agent_execution import AgentExecutor, RequestContext
591
+ from a2a.server.events import EventQueue
592
+ from a2a.types import TaskStatusUpdateEvent, TaskState, TaskArtifactUpdateEvent
593
+ from a2a.utils import new_agent_text_message, new_text_artifact
594
+
595
+ class MinimalExecutor(AgentExecutor):
596
+ @override
597
+ async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
598
+ row = context.get_context_data()["row"]
599
+ context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id")
600
+ task_id = row.get("id")
601
+ user_input = context.get_user_input()
602
+
603
+ # 진행 상태
604
+ event_queue.enqueue_event(
605
+ TaskStatusUpdateEvent(
606
+ status={
607
+ "state": TaskState.working,
608
+ "message": new_agent_text_message("처리중", context_id, task_id),
609
+ },
610
+ final=False,
611
+ contextId=context_id,
612
+ taskId=task_id,
613
+ metadata={"event_type": "task_started"}
614
+ )
615
+ )
616
+
617
+ await asyncio.sleep(1)
618
+
619
+ # 최종 결과
620
+ result = {"input": user_input, "output": "처리 완료"}
621
+ artifact = new_text_artifact(
622
+ name="결과",
623
+ description="처리 결과",
624
+ text=json.dumps(result, ensure_ascii=False)
625
+ )
626
+ event_queue.enqueue_event(
627
+ TaskArtifactUpdateEvent(
628
+ artifact=artifact,
629
+ lastChunk=True,
630
+ contextId=context_id,
631
+ taskId=task_id,
632
+ )
633
+ )
634
+
635
+ @override
636
+ async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
637
+ pass
638
+ ```
639
+
640
+ ## 🔧 실행 방법
641
+
642
+ ### 개발 환경에서 실행
643
+ ```bash
644
+ python sample_server/minimal_server.py
645
+ ```
646
+
647
+ ### 실제 사용시
648
+ ```bash
649
+ python my_server.py
650
+ ```
651
+
652
+ ## 🚀 배포/버전업 (PyPI/TestPyPI)
653
+
654
+ - 사전 준비: PyPI 혹은 TestPyPI 토큰 환경변수 설정
655
+
656
+ macOS/Linux:
657
+ ```bash
658
+ # TestPyPI 배포 예시
659
+ export TEST_PYPI_TOKEN="<your_testpypi_token>"
660
+ ./release.sh 0.3.13 testpypi
661
+
662
+ # PyPI 배포 예시
663
+ export PYPI_TOKEN="<your_pypi_token>"
664
+ ./release.sh 0.3.13 pypi
665
+ ```
666
+
667
+ Windows PowerShell:
668
+ ```powershell
669
+ # TestPyPI 배포 예시
670
+ $env:TEST_PYPI_TOKEN="<your_testpypi_token>"
671
+ ./release.ps1 -Version 0.3.13 -TestPyPI
672
+
673
+ # PyPI 배포 예시
674
+ $env:PYPI_TOKEN="<your_pypi_token>"
675
+ ./release.ps1 -Version 0.3.13
676
+ ```
677
+
678
+ ---
679
+
680
+ ## 📚 레퍼런스
681
+
682
+ ### 주요 함수들
683
+ - `ProcessGPTAgentServer.run()`: 서버 시작
684
+ - `new_agent_text_message(text, context_id, task_id)`: 상태 메시지 생성
685
+ - `new_text_artifact(name, desc, text)`: 결과 아티팩트 생성
686
+
687
+ ### 이벤트 저장 규칙
688
+ - **TaskStatusUpdateEvent** → `events` 테이블 (`data` 컬럼)
689
+ - **TaskArtifactUpdateEvent** → `todolist` 테이블 (`output` 컬럼)
690
+ - 래퍼 자동 제거 후 순수 payload만 저장
@@ -0,0 +1,7 @@
1
+ processgpt_agent_sdk/__init__.py,sha256=-Kwyodop7dRL1qy3UjW06X0vJoDqd2-cVlbesplIUmc,1108
2
+ processgpt_agent_sdk/database.py,sha256=G9nln9QTSfgPf3FfzLKwbXf3esEPz07rWVmzttxKTWE,18989
3
+ processgpt_agent_sdk/processgpt_agent_framework.py,sha256=QnYPV065da3bRL_xOYxaMOqQQUVv6D8CZ9d6cVL0-i4,16909
4
+ process_gpt_agent_sdk-0.3.13.dist-info/METADATA,sha256=NzdFo_djoSoyuEQJFr_-0SD3Cm_scQ72ZgpBt_4_hXw,22736
5
+ process_gpt_agent_sdk-0.3.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ process_gpt_agent_sdk-0.3.13.dist-info/top_level.txt,sha256=Xe6zrj3_3Vv7d0pl5RRtenVUckwOVBVLQn2P03j5REo,21
7
+ process_gpt_agent_sdk-0.3.13.dist-info/RECORD,,
@@ -0,0 +1,48 @@
1
+ """
2
+ ProcessGPT Agent SDK
3
+
4
+ 이 패키지는 ProcessGPT 시스템과 통합하기 위한 Agent Framework를 제공합니다.
5
+ """
6
+
7
+ from .processgpt_agent_framework import (
8
+ ProcessGPTAgentServer,
9
+ ProcessGPTRequestContext,
10
+ ProcessGPTEventQueue,
11
+ TodoListRowContext,
12
+ )
13
+
14
+ from .database import (
15
+ initialize_db,
16
+ polling_pending_todos,
17
+ record_event,
18
+ save_task_result,
19
+ update_task_error,
20
+ get_consumer_id,
21
+ fetch_agent_data,
22
+ fetch_all_agents,
23
+ fetch_form_types,
24
+ fetch_tenant_mcp_config,
25
+ fetch_human_users_by_proc_inst_id,
26
+ )
27
+
28
+ __version__ = "0.3.12"
29
+
30
+ __all__ = [
31
+ # Framework classes
32
+ "ProcessGPTAgentServer",
33
+ "ProcessGPTRequestContext",
34
+ "ProcessGPTEventQueue",
35
+ "TodoListRowContext",
36
+ # Database functions
37
+ "initialize_db",
38
+ "polling_pending_todos",
39
+ "record_event",
40
+ "save_task_result",
41
+ "update_task_error",
42
+ "get_consumer_id",
43
+ "fetch_agent_data",
44
+ "fetch_all_agents",
45
+ "fetch_form_types",
46
+ "fetch_tenant_mcp_config",
47
+ "fetch_human_users_by_proc_inst_id",
48
+ ]
@@ -210,7 +210,7 @@ async def fetch_all_agents() -> List[Dict[str, Any]]:
210
210
  client = get_db_client()
211
211
  return (
212
212
  client.table("users")
213
- .select("id, username, role, goal, persona, tools, profile, model, tenant_id, is_agent")
213
+ .select("id, username, role, goal, persona, tools, profile, model, tenant_id, is_agent, endpoint")
214
214
  .eq("is_agent", True)
215
215
  .execute()
216
216
  )
@@ -388,7 +388,7 @@ class ProcessGPTAgentServer:
388
388
  async def mark_task_failed(self, todolist_id: str, error_message: str):
389
389
  """태스크 실패 처리 (DB 상태 업데이트)"""
390
390
  try:
391
- await update_task_error(todolist_id)
391
+ await update_task_error(todolist_id, error_message)
392
392
  except Exception as e:
393
393
  logger.exception(
394
394
  "mark_task_failed error (todolist_id=%s): %s",
@@ -1,410 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: process-gpt-agent-sdk
3
- Version: 0.3.12
4
- Summary: Supabase 기반 이벤트/작업 폴링으로 A2A AgentExecutor를 실행하는 SDK
5
- License: MIT
6
- Project-URL: Homepage, https://github.com/your-org/process-gpt-agent-sdk
7
- Project-URL: Issues, https://github.com/your-org/process-gpt-agent-sdk/issues
8
- Keywords: agent,a2a,supabase,workflow,sdk,processgpt
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3 :: Only
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Requires-Python: >=3.9
14
- Description-Content-Type: text/markdown
15
- Requires-Dist: supabase>=2.0.0
16
- Requires-Dist: python-dotenv>=1.0.0
17
- Requires-Dist: click>=8.0.0
18
- Requires-Dist: asyncio-mqtt>=0.13.0
19
- Requires-Dist: jsonschema>=4.0.0
20
- Requires-Dist: structlog>=23.0.0
21
- Requires-Dist: typing-extensions>=4.0.0
22
- Requires-Dist: python-dateutil>=2.8.0
23
- Requires-Dist: a2a-sdk==0.3.0
24
-
25
- # ProcessGPT Agent Framework
26
- ## A2A SDK 연동을 위한 경량 에이전트 서버 프레임워크
27
-
28
- Supabase 기반의 프로세스 작업(Todolist)을 폴링하고, A2A 규격 이벤트를 통해 작업 상태/결과를 기록하는 **경량 에이전트 서버 프레임워크**입니다.
29
-
30
- ### 📋 요구사항
31
- - **런타임**: Python 3.9+ (권장: Python 3.11)
32
- - **데이터베이스**: Supabase (PostgreSQL) + 제공된 RPC/테이블
33
- - **이벤트 규격**: A2A `TaskStatusUpdateEvent` / `TaskArtifactUpdateEvent`
34
-
35
- ## 📊 이벤트 타입별 저장 테이블 및 특징
36
-
37
- ### 1. TaskStatusUpdateEvent (작업 상태 이벤트)
38
- - **저장 테이블**: `events`
39
- - **용도**: 작업 진행 상황, 사용자 입력 요청, 에러 알림 등
40
- - **저장 데이터**: 메시지 래퍼를 제거한 순수 payload만 `data` 컬럼에 JSON으로 저장
41
-
42
- ```python
43
- # 예시 코드
44
- event_queue.enqueue_event(
45
- TaskStatusUpdateEvent(
46
- status={
47
- "state": TaskState.working, # working, input_required, completed 등
48
- "message": new_agent_text_message("진행 중입니다", context_id, task_id),
49
- },
50
- final=False,
51
- contextId=context_id,
52
- taskId=task_id,
53
- metadata={"event_type": "task_started"} # events.event_type에 저장
54
- )
55
- )
56
- ```
57
-
58
- **특별 규칙**:
59
- - `state=input_required`일 때는 자동으로 `event_type=human_asked`로 저장됨
60
- - 메시지는 `new_agent_text_message()` 유틸 함수로 생성
61
-
62
- ### 2. TaskArtifactUpdateEvent (작업 결과 이벤트)
63
- - **저장 테이블**: `todolist` (output 컬럼)
64
- - **용도**: 최종 작업 결과물 전송
65
- - **저장 데이터**: 아티팩트 래퍼를 제거한 순수 payload만 `output` 컬럼에 JSON으로 저장
66
-
67
- ```python
68
- # 예시 코드
69
- artifact = new_text_artifact(
70
- name="처리결과",
71
- description="작업 완료 결과",
72
- text="실제 결과 데이터"
73
- )
74
- event_queue.enqueue_event(
75
- TaskArtifactUpdateEvent(
76
- artifact=artifact,
77
- lastChunk=True, # 최종 결과면 True
78
- contextId=context_id,
79
- taskId=task_id,
80
- )
81
- )
82
- ```
83
-
84
- **특별 규칙**:
85
- - `lastChunk=True` 또는 `final=True`일 때만 최종 저장됨 (`p_final=true`)
86
- - 아티팩트는 `new_text_artifact()` 유틸 함수로 생성
87
-
88
- ## 🔄 데이터 흐름과 값 전달 방식
89
-
90
- ### 전체 흐름
91
- 1. **작업 폴링**: 서버가 Supabase `todolist` 테이블에서 새 작업을 가져옴
92
- 2. **컨텍스트 준비**: `RequestContext`에 작업 정보와 사용자 입력을 담음
93
- 3. **익스큐터 실행**: 사용자가 구현한 `AgentExecutor.execute()` 메서드 호출
94
- 4. **이벤트 전송**: 익스큐터에서 진행 상황과 결과를 이벤트로 전송
95
- 5. **데이터 저장**: 이벤트 타입에 따라 적절한 테이블에 저장
96
-
97
- ### 값 전달 과정
98
- ```python
99
- # 1. 서버에서 작업 정보 가져오기
100
- row = context.get_context_data()["row"] # todolist 테이블의 한 행
101
- context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id") # 프로세스 ID
102
- task_id = row.get("id") # 작업 ID
103
- user_input = context.get_user_input() # 사용자가 입력한 내용
104
-
105
- # 2. 메시지/아티팩트 생성시 JSON 문자열로 변환
106
- payload = {"result": "처리 완료"}
107
- message_text = json.dumps(payload, ensure_ascii=False) # 중요: JSON 문자열로!
108
-
109
- # 3. 서버가 자동으로 래퍼 제거 후 순수 payload만 저장
110
- # events.data 또는 todolist.output에 {"result": "처리 완료"}만 저장됨
111
- ```
112
-
113
- ## 🚀 빠른 시작 가이드
114
-
115
- ### 1단계: 설치
116
- ```bash
117
- # 패키지 설치
118
- pip install -e .
119
-
120
- # 또는 requirements.txt 사용
121
- pip install -r requirements.txt
122
- ```
123
-
124
- ### 2단계: 환경 설정
125
- `.env` 파일 생성:
126
- ```env
127
- SUPABASE_URL=your_supabase_project_url
128
- SUPABASE_KEY=your_supabase_anon_key
129
- ENV=dev
130
- ```
131
-
132
- ### 3단계: 서버 구현 방법
133
- 서버는 이렇게 만드세요:
134
-
135
- ```python
136
- # my_server.py
137
- import asyncio
138
- from dotenv import load_dotenv
139
- from processgpt_agent_sdk.processgpt_agent_framework import ProcessGPTAgentServer
140
- from my_executor import MyExecutor # 아래에서 구현할 익스큐터
141
-
142
- async def main():
143
- load_dotenv()
144
-
145
- server = ProcessGPTAgentServer(
146
- agent_executor=MyExecutor(), # 여러분이 구현할 익스큐터
147
- agent_type="my-agent" # Supabase todolist.agent_orch와 매칭되어야 함
148
- )
149
- server.polling_interval = 3 # 3초마다 새 작업 확인
150
-
151
- print("서버 시작!")
152
- await server.run()
153
-
154
- if __name__ == "__main__":
155
- try:
156
- asyncio.run(main())
157
- except KeyboardInterrupt:
158
- print("서버 종료")
159
- ```
160
-
161
- ### 4단계: 익스큐터 구현 방법
162
- 익스큐터는 이렇게 만드세요:
163
-
164
- ```python
165
- # my_executor.py
166
- import asyncio
167
- import json
168
- from typing_extensions import override
169
- from a2a.server.agent_execution import AgentExecutor, RequestContext
170
- from a2a.server.events import EventQueue
171
- from a2a.types import TaskStatusUpdateEvent, TaskState, TaskArtifactUpdateEvent
172
- from a2a.utils import new_agent_text_message, new_text_artifact
173
-
174
- class MyExecutor(AgentExecutor):
175
- @override
176
- async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
177
- # 1. 작업 정보 가져오기
178
- row = context.get_context_data()["row"]
179
- context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id")
180
- task_id = row.get("id")
181
- user_input = context.get_user_input() # 사용자가 입력한 내용
182
-
183
- print(f"처리할 작업: {user_input}")
184
-
185
- # 2. 작업 시작 알림 (events 테이블에 저장됨)
186
- event_queue.enqueue_event(
187
- TaskStatusUpdateEvent(
188
- status={
189
- "state": TaskState.working,
190
- "message": new_agent_text_message("작업 시작", context_id, task_id),
191
- },
192
- final=False,
193
- contextId=context_id,
194
- taskId=task_id,
195
- metadata={"event_type": "task_started"}
196
- )
197
- )
198
-
199
- # 3. 실제 작업 수행 (여기에 여러분의 로직 작성)
200
- await asyncio.sleep(2)
201
- result_data = {"status": "완료", "input": user_input, "output": "처리 결과"}
202
-
203
- # 4. 최종 결과 전송 (todolist.output에 저장됨)
204
- artifact = new_text_artifact(
205
- name="처리결과",
206
- description="작업 완료 결과",
207
- text=json.dumps(result_data, ensure_ascii=False) # JSON 문자열로!
208
- )
209
- event_queue.enqueue_event(
210
- TaskArtifactUpdateEvent(
211
- artifact=artifact,
212
- lastChunk=True, # 중요: 최종 결과면 True
213
- contextId=context_id,
214
- taskId=task_id,
215
- )
216
- )
217
-
218
- @override
219
- async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
220
- pass # 취소 로직 (필요시 구현)
221
- ```
222
-
223
- ### 5단계: 실행
224
- ```bash
225
- python my_server.py
226
- ```
227
-
228
- ## 🤝 Human-in-the-Loop (사용자 입력 요청) 패턴
229
-
230
- 사용자 입력이 필요할 때:
231
-
232
- ```python
233
- # 사용자 입력 요청
234
- question_data = {
235
- "question": "어떤 방식으로 처리할까요?",
236
- "options": ["방식A", "방식B", "방식C"]
237
- }
238
-
239
- event_queue.enqueue_event(
240
- TaskStatusUpdateEvent(
241
- status={
242
- "state": TaskState.input_required, # 이 상태가 중요!
243
- "message": new_agent_text_message(
244
- json.dumps(question_data, ensure_ascii=False),
245
- context_id, task_id
246
- ),
247
- },
248
- final=True,
249
- contextId=context_id,
250
- taskId=task_id,
251
- metadata={"job_id": f"job-{task_id}"} # job_id 필수
252
- )
253
- )
254
- # 자동으로 events 테이블에 event_type=human_asked로 저장됨
255
- ```
256
-
257
- ## 📋 체크리스트 (실패 없는 통합을 위한)
258
-
259
- ### 필수 설정
260
- - [ ] `.env`에 `SUPABASE_URL`, `SUPABASE_KEY` 설정
261
- - [ ] `requirements.txt` 설치 완료
262
- - [ ] Supabase에서 제공 SQL(`database_schema.sql`, `function.sql`) 적용
263
-
264
- ### 코드 구현
265
- - [ ] 서버에서 `agent_type`이 Supabase `todolist.agent_orch`와 매칭됨
266
- - [ ] 익스큐터에서 `contextId`, `taskId`를 올바르게 설정
267
- - [ ] 상태 이벤트는 `new_agent_text_message()`로 생성
268
- - [ ] 최종 결과는 `new_text_artifact()` + `lastChunk=True`로 전송
269
- - [ ] HITL 요청시 `TaskState.input_required` 사용
270
-
271
- ## 🚨 자주 발생하는 문제
272
-
273
- ### 1. 설치 문제
274
- **증상**: `ModuleNotFoundError`
275
- ```bash
276
- # 해결
277
- pip install -e .
278
- pip install a2a-sdk==0.3.0 --force-reinstall
279
- ```
280
-
281
- ### 2. 작업이 폴링되지 않음
282
- **원인**: Supabase 연결 문제
283
- **해결**:
284
- - `.env` 파일 위치 확인 (프로젝트 루트)
285
- - URL/Key 재확인
286
- - `agent_type`이 todolist.agent_orch와 매칭되는지 확인
287
-
288
- ### 3. 이벤트가 저장되지 않음
289
- **원인**: 테이블/함수 누락
290
- **해결**:
291
- - `database_schema.sql`, `function.sql` 실행 확인
292
- - Supabase 테이블 권한 확인
293
-
294
- ### 4. 결과가 래퍼와 함께 저장됨
295
- **원인**: JSON 문자열 변환 누락
296
- ```python
297
- # 올바른 방법
298
- text=json.dumps(data, ensure_ascii=False) # JSON 문자열로!
299
-
300
- # 잘못된 방법
301
- text=data # 딕셔너리 직접 전달 (X)
302
- ```
303
-
304
- ## 📚 샘플 코드 (간단 버전)
305
-
306
- ### 기본 서버
307
- ```python
308
- # sample_server/minimal_server.py
309
- import asyncio
310
- from dotenv import load_dotenv
311
- from processgpt_agent_sdk.processgpt_agent_framework import ProcessGPTAgentServer
312
- from sample_server.minimal_executor import MinimalExecutor
313
-
314
- async def main():
315
- load_dotenv()
316
- server = ProcessGPTAgentServer(
317
- agent_executor=MinimalExecutor(),
318
- agent_type="crewai-action"
319
- )
320
- server.polling_interval = 3
321
- await server.run()
322
-
323
- if __name__ == "__main__":
324
- try:
325
- asyncio.run(main())
326
- except KeyboardInterrupt:
327
- pass
328
- ```
329
-
330
- ### 기본 익스큐터
331
- ```python
332
- # sample_server/minimal_executor.py
333
- import asyncio
334
- import json
335
- from typing_extensions import override
336
- from a2a.server.agent_execution import AgentExecutor, RequestContext
337
- from a2a.server.events import EventQueue
338
- from a2a.types import TaskStatusUpdateEvent, TaskState, TaskArtifactUpdateEvent
339
- from a2a.utils import new_agent_text_message, new_text_artifact
340
-
341
- class MinimalExecutor(AgentExecutor):
342
- @override
343
- async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
344
- row = context.get_context_data()["row"]
345
- context_id = row.get("root_proc_inst_id") or row.get("proc_inst_id")
346
- task_id = row.get("id")
347
- user_input = context.get_user_input()
348
-
349
- # 진행 상태
350
- event_queue.enqueue_event(
351
- TaskStatusUpdateEvent(
352
- status={
353
- "state": TaskState.working,
354
- "message": new_agent_text_message("처리중", context_id, task_id),
355
- },
356
- final=False,
357
- contextId=context_id,
358
- taskId=task_id,
359
- metadata={"event_type": "task_started"}
360
- )
361
- )
362
-
363
- await asyncio.sleep(1)
364
-
365
- # 최종 결과
366
- result = {"input": user_input, "output": "처리 완료"}
367
- artifact = new_text_artifact(
368
- name="결과",
369
- description="처리 결과",
370
- text=json.dumps(result, ensure_ascii=False)
371
- )
372
- event_queue.enqueue_event(
373
- TaskArtifactUpdateEvent(
374
- artifact=artifact,
375
- lastChunk=True,
376
- contextId=context_id,
377
- taskId=task_id,
378
- )
379
- )
380
-
381
- @override
382
- async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
383
- pass
384
- ```
385
-
386
- ## 🔧 실행 방법
387
-
388
- ### 개발 환경에서 실행
389
- ```bash
390
- python sample_server/minimal_server.py
391
- ```
392
-
393
- ### 실제 사용시
394
- ```bash
395
- python my_server.py
396
- ```
397
-
398
- ---
399
-
400
- ## 📚 레퍼런스
401
-
402
- ### 주요 함수들
403
- - `ProcessGPTAgentServer.run()`: 서버 시작
404
- - `new_agent_text_message(text, context_id, task_id)`: 상태 메시지 생성
405
- - `new_text_artifact(name, desc, text)`: 결과 아티팩트 생성
406
-
407
- ### 이벤트 저장 규칙
408
- - **TaskStatusUpdateEvent** → `events` 테이블 (`data` 컬럼)
409
- - **TaskArtifactUpdateEvent** → `todolist` 테이블 (`output` 컬럼)
410
- - 래퍼 자동 제거 후 순수 payload만 저장
@@ -1,6 +0,0 @@
1
- processgpt_agent_sdk/database.py,sha256=k219S54XwQoGJcKYq6Zr0f7j-Dtrp-v7SpLMRnIgHCI,18979
2
- processgpt_agent_sdk/processgpt_agent_framework.py,sha256=YE5JB4TyRkYFYxRMRfo-SyXb5JuFZwaNtNaxFNd9PXA,16894
3
- process_gpt_agent_sdk-0.3.12.dist-info/METADATA,sha256=x5kNRsZ9J0jaCmYmWEhQiuDeiXqH_vgjaDkZ8jizLSE,13600
4
- process_gpt_agent_sdk-0.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- process_gpt_agent_sdk-0.3.12.dist-info/top_level.txt,sha256=Xe6zrj3_3Vv7d0pl5RRtenVUckwOVBVLQn2P03j5REo,21
6
- process_gpt_agent_sdk-0.3.12.dist-info/RECORD,,