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

@@ -23,6 +23,10 @@ from .utils.event_handler import process_event_message
23
23
  from .utils.context_manager import set_context, reset_context
24
24
 
25
25
 
26
+ # =============================================================================
27
+ # 서버: ProcessGPTAgentServer
28
+ # 설명: 작업 폴링→실행 준비→실행/이벤트 저장→취소 감시까지 담당하는 핵심 서버
29
+ # =============================================================================
26
30
  class ProcessGPTAgentServer:
27
31
  """ProcessGPT 핵심 서버
28
32
 
@@ -31,6 +35,7 @@ class ProcessGPTAgentServer:
31
35
  """
32
36
 
33
37
  def __init__(self, executor: AgentExecutor, polling_interval: int = 5, agent_orch: str = ""):
38
+ """서버 실행기/폴링 주기/오케스트레이션 값을 초기화한다."""
34
39
  self.polling_interval = polling_interval
35
40
  self.is_running = False
36
41
  self._executor: AgentExecutor = executor
@@ -39,6 +44,7 @@ class ProcessGPTAgentServer:
39
44
  initialize_db()
40
45
 
41
46
  async def run(self) -> None:
47
+ """메인 폴링 루프를 실행한다. 작업을 가져와 준비/실행/감시를 순차 수행."""
42
48
  self.is_running = True
43
49
  write_log_message("ProcessGPT 서버 시작")
44
50
 
@@ -64,7 +70,6 @@ class ProcessGPTAgentServer:
64
70
  await update_task_error(str(task_id))
65
71
  except Exception as upd_err:
66
72
  handle_application_error("FAILED 상태 업데이트 실패", upd_err, raise_error=False)
67
- # 다음 루프로 진행
68
73
  continue
69
74
 
70
75
  except Exception as e:
@@ -72,10 +77,12 @@ class ProcessGPTAgentServer:
72
77
  await asyncio.sleep(self.polling_interval)
73
78
 
74
79
  def stop(self) -> None:
80
+ """폴링 루프를 중지 플래그로 멈춘다."""
75
81
  self.is_running = False
76
82
  write_log_message("ProcessGPT 서버 중지")
77
83
 
78
84
  async def _prepare_service_data(self, task_record: Dict[str, Any]) -> Dict[str, Any]:
85
+ """실행에 필요한 데이터(에이전트/폼/요약/사용자)를 준비해 dict로 반환."""
79
86
  done_outputs = await fetch_done_data(task_record.get("proc_inst_id"))
80
87
  write_log_message(f"[PREP] done_outputs → {done_outputs}")
81
88
  feedbacks = task_record.get("feedback")
@@ -121,12 +128,12 @@ class ProcessGPTAgentServer:
121
128
  return prepared
122
129
 
123
130
  async def _execute_with_cancel_watch(self, task_record: Dict[str, Any], prepared_data: Dict[str, Any]) -> None:
131
+ """실행 태스크와 취소 감시 태스크를 동시에 운영한다."""
124
132
  executor = self._executor
125
133
 
126
134
  context = ProcessGPTRequestContext(prepared_data)
127
135
  event_queue = ProcessGPTEventQueue(task_record)
128
136
 
129
- # 실행 전 컨텍스트 변수 설정 (CrewAI 전역 리스너 등에서 활용)
130
137
  try:
131
138
  set_context(
132
139
  todo_id=str(task_record.get("id")),
@@ -167,6 +174,7 @@ class ProcessGPTAgentServer:
167
174
  write_log_message(f"[EXEC END] task_id={task_record.get('id')} agent={prepared_data.get('agent_orch','')}")
168
175
 
169
176
  async def _watch_cancellation(self, task_record: Dict[str, Any], executor: AgentExecutor, context: RequestContext, event_queue: EventQueue, execute_task: asyncio.Task) -> None:
177
+ """작업 상태를 주기적으로 확인해 취소 신호 시 안전 종료를 수행."""
170
178
  todo_id = str(task_record.get("id"))
171
179
 
172
180
  while True:
@@ -192,34 +200,47 @@ class ProcessGPTAgentServer:
192
200
  handle_application_error("취소 후 이벤트 큐 종료 실패", e, raise_error=False)
193
201
  break
194
202
 
195
-
203
+ # =============================================================================
204
+ # 요청 컨텍스트: ProcessGPTRequestContext
205
+ # 설명: 실행기에게 전달되는 요청 데이터/상태를 캡슐화
206
+ # =============================================================================
196
207
  class ProcessGPTRequestContext(RequestContext):
197
208
  def __init__(self, prepared_data: Dict[str, Any]):
209
+ """실행에 필요한 데이터 묶음을 보관한다."""
198
210
  self._prepared_data = prepared_data
199
211
  self._message = prepared_data.get("message", "")
200
212
  self._current_task = None
201
213
 
202
214
  def get_user_input(self) -> str:
215
+ """사용자 입력 메시지를 반환한다."""
203
216
  return self._message
204
217
 
205
218
  @property
206
219
  def message(self) -> str:
220
+ """현재 메시지(사용자 입력)를 반환한다."""
207
221
  return self._message
208
222
 
209
223
  @property
210
224
  def current_task(self):
225
+ """현재 실행 중 태스크(있다면)를 반환한다."""
211
226
  return getattr(self, "_current_task", None)
212
227
 
213
228
  def get_context_data(self) -> Dict[str, Any]:
229
+ """실행 컨텍스트 전체 데이터를 dict로 반환한다."""
214
230
  return self._prepared_data
215
231
 
216
-
232
+ # =============================================================================
233
+ # 이벤트 큐: ProcessGPTEventQueue
234
+ # 설명: 실행기 이벤트를 내부 큐에 넣고, 비동기 처리 태스크를 생성해 저장 로직 호출
235
+ # =============================================================================
217
236
  class ProcessGPTEventQueue(EventQueue):
218
237
  def __init__(self, task_record: Dict[str, Any]):
238
+ """현재 처리 중인 작업 레코드를 보관한다."""
219
239
  self.todo = task_record
220
240
  super().__init__()
221
241
 
222
242
  def enqueue_event(self, event: Event):
243
+ """이벤트를 큐에 넣고, 백그라운드로 DB 저장 코루틴을 실행한다."""
223
244
  try:
224
245
  try:
225
246
  super().enqueue_event(event)
@@ -231,15 +252,18 @@ class ProcessGPTEventQueue(EventQueue):
231
252
  handle_application_error("이벤트 저장 실패", e, raise_error=False)
232
253
 
233
254
  def task_done(self) -> None:
255
+ """태스크 완료 로그를 남긴다."""
234
256
  try:
235
257
  write_log_message(f"태스크 완료: {self.todo['id']}")
236
258
  except Exception as e:
237
259
  handle_application_error("태스크 완료 처리 실패", e, raise_error=False)
238
260
 
239
261
  async def close(self) -> None:
262
+ """큐 종료 훅(필요 시 리소스 정리)."""
240
263
  pass
241
264
 
242
265
  def _create_bg_task(self, coro: Any, label: str) -> None:
266
+ """백그라운드 태스크 생성 및 완료 콜백으로 예외 로깅."""
243
267
  try:
244
268
  task = asyncio.create_task(coro)
245
269
  def _cb(t: asyncio.Task):
@@ -1,21 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
+ # =============================================================================
4
+ # Imports
5
+ # =============================================================================
6
+
3
7
  import time
4
8
  import uuid
5
- import os
6
9
  from typing import Optional, List, Literal, Type, Dict, Any
10
+ from datetime import datetime, timezone
7
11
 
8
12
  from pydantic import BaseModel, Field
9
13
  from crewai.tools import BaseTool
10
14
 
11
- from ..utils.crewai_event_listener import CrewAIEventLogger
12
15
  from ..utils.context_manager import todo_id_var, proc_id_var, all_users_var
13
16
  from ..utils.logger import write_log_message, handle_application_error
14
- from ..core.database import fetch_human_response, save_notification
17
+ from ..core.database import fetch_human_response_sync, save_notification, initialize_db, get_db_client
15
18
 
16
19
 
20
+ # =============================================================================
21
+ # 입력 스키마
22
+ # =============================================================================
17
23
  class HumanQuerySchema(BaseModel):
18
- """사용자 확인/추가정보 요청용 스키마"""
24
+ """사용자 확인/추가정보 요청 입력 스키마."""
19
25
 
20
26
  role: str = Field(..., description="누구에게(역할 또는 대상)")
21
27
  text: str = Field(..., description="질의 내용")
@@ -27,8 +33,12 @@ class HumanQuerySchema(BaseModel):
27
33
  )
28
34
 
29
35
 
36
+ # =============================================================================
37
+ # HumanQueryTool
38
+ # 설명: 보안/모호성 관련 질문을 사용자에게 전송하고 응답을 동기 대기
39
+ # =============================================================================
30
40
  class HumanQueryTool(BaseTool):
31
- """사람에게 보안/모호성 관련 확인을 요청하고 응답을 대기하는 도구"""
41
+ """사람에게 질문을 보내고 응답을 동기적으로 대기한다."""
32
42
 
33
43
  name: str = "human_asked"
34
44
  description: str = (
@@ -95,6 +105,7 @@ class HumanQueryTool(BaseTool):
95
105
  agent_name: Optional[str] = None,
96
106
  **kwargs,
97
107
  ):
108
+ """도구 실행 컨텍스트(테넌트/유저/프로세스)를 옵션으로 설정."""
98
109
  super().__init__(**kwargs)
99
110
  self._tenant_id = tenant_id
100
111
  self._user_id = user_id
@@ -102,18 +113,19 @@ class HumanQueryTool(BaseTool):
102
113
  self._proc_inst_id = proc_inst_id
103
114
  self._agent_name = agent_name
104
115
 
105
- # 동기 실행: CrewAI Tool 실행 컨텍스트에서 블로킹 폴링 허용
116
+ # =============================================================================
117
+ # 동기 실행
118
+ # 설명: 질문 이벤트를 저장하고 DB 폴링으로 응답을 기다린다
119
+ # =============================================================================
106
120
  def _run(
107
121
  self, role: str, text: str, type: str = "text", options: Optional[List[str]] = None
108
122
  ) -> str:
123
+ """질문 이벤트를 기록하고 최종 응답 문자열을 반환."""
109
124
  try:
110
- # 초기화된 기본 agent_name 사용
111
125
  agent_name = getattr(self, "_agent_name", None)
112
-
113
126
  write_log_message(f"HumanQueryTool 실행: role={role}, agent_name={agent_name}, type={type}, options={options}")
114
127
  query_id = f"human_asked_{uuid.uuid4()}"
115
128
 
116
- # 이벤트 발행 데이터
117
129
  payload: Dict[str, Any] = {
118
130
  "role": role,
119
131
  "text": text,
@@ -121,31 +133,31 @@ class HumanQueryTool(BaseTool):
121
133
  "options": options or [],
122
134
  }
123
135
 
124
- # 컨텍스트 식별자
125
136
  todo_id = todo_id_var.get() or self._todo_id
126
137
  proc_inst_id = proc_id_var.get() or self._proc_inst_id
127
138
 
128
- # 이벤트 발행
129
- # 상태 정보는 data 안에 포함시켜 저장 (emit_event 시그니처에 status 없음)
130
139
  payload_with_status = {
131
140
  **payload,
132
141
  "status": "ASKED",
133
142
  "agent_profile": "/images/chat-icon.png"
134
143
  }
135
- ev = CrewAIEventLogger()
136
- ev.emit_event(
137
- event_type="human_asked",
138
- data=payload_with_status,
139
- job_id=query_id,
140
- crew_type="action",
141
- todo_id=str(todo_id) if todo_id is not None else None,
142
- proc_inst_id=str(proc_inst_id) if proc_inst_id is not None else None,
143
- )
144
-
145
- # 알림 저장 (notifications 테이블)
144
+
145
+ initialize_db()
146
+ supabase = get_db_client()
147
+ record = {
148
+ "id": str(uuid.uuid4()),
149
+ "job_id": query_id,
150
+ "todo_id": str(todo_id) if todo_id is not None else None,
151
+ "proc_inst_id": str(proc_inst_id) if proc_inst_id is not None else None,
152
+ "event_type": "human_asked",
153
+ "crew_type": "action",
154
+ "data": payload_with_status,
155
+ "timestamp": datetime.now(timezone.utc).isoformat(),
156
+ }
157
+ supabase.table("events").insert(record).execute()
158
+
146
159
  try:
147
160
  tenant_id = self._tenant_id
148
- # 대상 이메일: context var(all_users_var)에 이메일 CSV가 있어야만 저장
149
161
  target_emails_csv = all_users_var.get() or ""
150
162
  if target_emails_csv and target_emails_csv.strip():
151
163
  write_log_message(f"알림 저장 시도: target_emails_csv={target_emails_csv}, tenant_id={tenant_id}")
@@ -163,41 +175,37 @@ class HumanQueryTool(BaseTool):
163
175
  except Exception as e:
164
176
  handle_application_error("알림저장HumanTool", e, raise_error=False)
165
177
 
166
- # 응답 폴링 (events 테이블에서 동일 job_id, event_type=human_response)
167
178
  answer = self._wait_for_response(query_id)
168
179
  return answer
169
180
  except Exception as e:
170
- # 사용자 미응답 또는 기타 에러 시에도 작업이 즉시 중단되지 않도록 문자열 반환
171
181
  handle_application_error("HumanQueryTool", e, raise_error=False)
172
182
  return "사용자 미응답 거절"
173
183
 
184
+ # =============================================================================
185
+ # 응답 대기
186
+ # 설명: events 테이블에서 human_response를 폴링하여 응답을 가져온다
187
+ # =============================================================================
174
188
  def _wait_for_response(
175
189
  self, job_id: str, timeout_sec: int = 180, poll_interval_sec: int = 5
176
190
  ) -> str:
177
- """DB events 테이블을 폴링하여 사람의 응답을 기다림"""
191
+ """DB 폴링으로 사람의 응답을 기다려 문자열로 반환."""
178
192
  deadline = time.time() + timeout_sec
179
193
 
180
194
  while time.time() < deadline:
181
195
  try:
182
196
  write_log_message(f"HumanQueryTool 응답 폴링: {job_id}")
183
- event = fetch_human_response(job_id=job_id)
197
+ event = fetch_human_response_sync(job_id=job_id)
184
198
  if event:
185
199
  write_log_message(f"HumanQueryTool 응답 수신: {event}")
186
200
  data = event.get("data") or {}
187
- # 기대 형식: {"answer": str, ...}
188
201
  answer = (data or {}).get("answer")
189
202
  if isinstance(answer, str):
190
203
  write_log_message("사람 응답 수신 완료")
191
204
  return answer
192
- # 문자열이 아니면 직렬화하여 반환
193
205
  return str(data)
194
206
 
195
207
  except Exception as e:
196
- # 응답이 아직 없는 경우(0개 행) 또는 기타 DB 오류 시 계속 폴링
197
208
  write_log_message(f"인간 응답 대기 중... (오류: {str(e)[:100]})")
198
-
199
209
  time.sleep(poll_interval_sec)
200
-
201
- # 타임아웃: 사용자 미응답으로 간주
202
210
  return "사용자 미응답 거절"
203
211
 
@@ -9,23 +9,30 @@ from .knowledge_tools import Mem0Tool, MementoTool
9
9
  from ..utils.logger import write_log_message, handle_application_error
10
10
 
11
11
 
12
+ # =============================================================================
13
+ # SafeToolLoader
14
+ # 설명: 로컬/외부 MCP 도구들을 안전하게 초기화·로드·종료 관리
15
+ # =============================================================================
12
16
  class SafeToolLoader:
13
17
  """도구 로더 클래스"""
14
- adapters = [] # MCPServerAdapter 인스턴스 등록
18
+ adapters = []
15
19
 
16
20
  ANYIO_PATCHED: bool = False
17
21
 
18
22
  def __init__(self, tenant_id: Optional[str] = None, user_id: Optional[str] = None, agent_name: Optional[str] = None, mcp_config: Optional[Dict] = None):
23
+ """실행 컨텍스트(tenant/user/agent)와 MCP 설정을 보관한다."""
19
24
  self.tenant_id = tenant_id
20
25
  self.user_id = user_id
21
26
  self.agent_name = agent_name
22
- # 외부에서 전달된 MCP 설정 사용 (DB 접근 금지)
23
27
  self._mcp_servers = (mcp_config or {}).get('mcpServers', {})
24
28
  self.local_tools = ["mem0", "memento", "human_asked"]
25
29
  write_log_message(f"SafeToolLoader 초기화 완료 (tenant_id: {tenant_id}, user_id: {user_id})")
26
30
 
31
+ # =============================================================================
32
+ # Warmup (npx 서버 사전 준비)
33
+ # =============================================================================
27
34
  def warmup_server(self, server_key: str, mcp_config: Optional[Dict] = None):
28
- """npx 기반 서버의 패키지를 미리 캐시에 저장해 실제 실행을 빠르게."""
35
+ """npx 서버 패키지를 미리 캐싱해 최초 실행 지연을 줄인다."""
29
36
  servers = (mcp_config or {}).get('mcpServers') or self._mcp_servers or {}
30
37
  server_config = servers.get(server_key, {}) if isinstance(servers, dict) else {}
31
38
  if not server_config or server_config.get("command") != "npx":
@@ -54,8 +61,11 @@ class SafeToolLoader:
54
61
  except Exception:
55
62
  pass
56
63
 
64
+ # =============================================================================
65
+ # 유틸: npx 경로 탐색
66
+ # =============================================================================
57
67
  def _find_npx_command(self) -> str:
58
- """npx 명령어 경로 찾기"""
68
+ """npx 실행 파일 경로를 탐색해 반환한다."""
59
69
  try:
60
70
  import shutil
61
71
  npx_path = shutil.which("npx") or shutil.which("npx.cmd")
@@ -65,6 +75,9 @@ class SafeToolLoader:
65
75
  pass
66
76
  return "npx"
67
77
 
78
+ # =============================================================================
79
+ # 로컬 도구 생성
80
+ # =============================================================================
68
81
  def create_tools_from_names(self, tool_names: List[str], mcp_config: Optional[Dict] = None) -> List:
69
82
  """tool_names 리스트에서 실제 Tool 객체들 생성"""
70
83
  if isinstance(tool_names, str):
@@ -88,6 +101,9 @@ class SafeToolLoader:
88
101
  write_log_message(f"총 {len(tools)}개 도구 생성 완료")
89
102
  return tools
90
103
 
104
+ # =============================================================================
105
+ # 로컬 도구 로더들
106
+ # =============================================================================
91
107
  def _load_mem0(self) -> List:
92
108
  """mem0 도구 로드 - 에이전트별 메모리"""
93
109
  try:
@@ -110,12 +126,14 @@ class SafeToolLoader:
110
126
  def _load_human_asked(self) -> List:
111
127
  """human_asked 도구 로드 (선택사항: 사용 시 외부에서 주입)"""
112
128
  try:
113
- # 필요한 경우 외부에서 HumanQueryTool을 이 패키지에 추가하여 import하고 리턴하도록 변경 가능
114
129
  return []
115
130
  except Exception as error:
116
131
  handle_application_error("툴human오류", error, raise_error=False)
117
132
  return []
118
133
 
134
+ # =============================================================================
135
+ # 외부 MCP 도구 로더
136
+ # =============================================================================
119
137
  def _load_mcp_tool(self, tool_name: str, mcp_config: Optional[Dict] = None) -> List:
120
138
  """MCP 도구 로드 (timeout & retry 지원)"""
121
139
  self._apply_anyio_patch()
@@ -157,8 +175,11 @@ class SafeToolLoader:
157
175
  handle_application_error(f"툴{tool_name}오류", e, raise_error=False)
158
176
  return []
159
177
 
178
+ # =============================================================================
179
+ # anyio 서브프로세스 stderr 패치
180
+ # =============================================================================
160
181
  def _apply_anyio_patch(self):
161
- """anyio stderr 패치 적용"""
182
+ """stderr fileno 없음 대비: PIPE로 보정해 예외를 방지한다."""
162
183
  if SafeToolLoader.ANYIO_PATCHED:
163
184
  return
164
185
  from anyio._core._subprocesses import open_process as _orig
@@ -173,9 +194,12 @@ class SafeToolLoader:
173
194
  anyio._core._subprocesses.open_process = patched_open_process
174
195
  SafeToolLoader.ANYIO_PATCHED = True
175
196
 
197
+ # =============================================================================
198
+ # 종료 처리
199
+ # =============================================================================
176
200
  @classmethod
177
201
  def shutdown_all_adapters(cls):
178
- """모든 MCPServerAdapter 연결 종료"""
202
+ """모든 MCPServerAdapter 연결을 안전하게 종료한다."""
179
203
  for adapter in cls.adapters:
180
204
  try:
181
205
  adapter.stop()
@@ -1,9 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ # =============================================================================
4
+ # Context Manager
5
+ # 설명: 요청/프로세스 범위의 컨텍스트 값을 ContextVar로 관리
6
+ # =============================================================================
7
+
3
8
  from contextvars import ContextVar
4
9
  from typing import Optional
5
10
 
6
11
 
12
+ # 컨텍스트 변수 정의
7
13
  todo_id_var: ContextVar[Optional[str]] = ContextVar("todo_id", default=None)
8
14
  proc_id_var: ContextVar[Optional[str]] = ContextVar("proc_id", default=None)
9
15
  crew_type_var: ContextVar[Optional[str]] = ContextVar("crew_type", default=None)
@@ -13,6 +19,7 @@ all_users_var: ContextVar[Optional[str]] = ContextVar("all_users", default=None)
13
19
 
14
20
 
15
21
  def set_context(*, todo_id: Optional[str] = None, proc_inst_id: Optional[str] = None, crew_type: Optional[str] = None, form_key: Optional[str] = None, form_id: Optional[str] = None, all_users: Optional[str] = None) -> None:
22
+ """전달된 값들만 ContextVar에 설정한다."""
16
23
  if todo_id is not None:
17
24
  todo_id_var.set(todo_id)
18
25
  if proc_inst_id is not None:
@@ -28,6 +35,7 @@ def set_context(*, todo_id: Optional[str] = None, proc_inst_id: Optional[str] =
28
35
 
29
36
 
30
37
  def reset_context() -> None:
38
+ """모든 컨텍스트 값을 초기 상태(None)로 되돌린다."""
31
39
  todo_id_var.set(None)
32
40
  proc_id_var.set(None)
33
41
  crew_type_var.set(None)
@@ -21,6 +21,7 @@ class CrewAIEventLogger:
21
21
  # Initialization
22
22
  # =============================================================================
23
23
  def __init__(self):
24
+ """Supabase 클라이언트를 초기화한다."""
24
25
  initialize_db()
25
26
  self.supabase = get_db_client()
26
27
  write_log_message("CrewAIEventLogger 초기화 완료")
@@ -151,7 +152,6 @@ class CrewAIEventLogger:
151
152
  except Exception as e:
152
153
  if attempt < 3:
153
154
  handle_application_error("이벤트저장오류(재시도)", e, raise_error=False)
154
- # 지수 백오프: 0.3s, 0.6s
155
155
  import time
156
156
  time.sleep(0.3 * attempt)
157
157
  continue
@@ -178,7 +178,10 @@ class CrewAIEventLogger:
178
178
  handle_application_error("이벤트처리오류", e, raise_error=False)
179
179
 
180
180
 
181
-
181
+ # =============================================================================
182
+ # CrewConfigManager
183
+ # 설명: 이벤트 리스너를 프로세스 단위로 1회 등록
184
+ # =============================================================================
182
185
  class CrewConfigManager:
183
186
  """글로벌 CrewAI 이벤트 리스너 등록 매니저"""
184
187
  _registered_by_pid: set[int] = set()
@@ -188,6 +191,7 @@ class CrewConfigManager:
188
191
  self._register_once_per_process()
189
192
 
190
193
  def _register_once_per_process(self) -> None:
194
+ """현재 프로세스에만 한 번 이벤트 리스너를 등록한다."""
191
195
  try:
192
196
  pid = os.getpid()
193
197
  if pid in self._registered_by_pid:
@@ -11,16 +11,25 @@ if not logging.getLogger().handlers:
11
11
  format="%(asctime)s %(levelname)s %(name)s - %(message)s",
12
12
  )
13
13
 
14
- APPLICATION_LOGGER = logging.getLogger("process-gpt-agent-framework")
14
+ LOGGER_NAME = os.getenv("LOGGER_NAME") or "processgpt"
15
+ APPLICATION_LOGGER = logging.getLogger(LOGGER_NAME)
16
+
17
+
18
+ def set_application_logger_name(name: str) -> None:
19
+ """애플리케이션 로거 이름을 런타임에 변경한다."""
20
+ global APPLICATION_LOGGER
21
+ APPLICATION_LOGGER = logging.getLogger(name or "processgpt")
15
22
 
16
23
 
17
24
  def write_log_message(message: str, level: int = logging.INFO) -> None:
25
+ """로그 메시지를 쓴다."""
18
26
  spaced = os.getenv("LOG_SPACED", "1") != "0"
19
27
  suffix = "\n" if spaced else ""
20
28
  APPLICATION_LOGGER.log(level, f"{message}{suffix}")
21
29
 
22
30
 
23
31
  def handle_application_error(title: str, error: Exception, *, raise_error: bool = True, extra: Optional[Dict] = None) -> None:
32
+ """예외 상황을 처리한다."""
24
33
  spaced = os.getenv("LOG_SPACED", "1") != "0"
25
34
  suffix = "\n" if spaced else ""
26
35
  context = f" | extra={extra}" if extra else ""