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

@@ -1,39 +1,97 @@
1
- import logging
2
- import os
3
- import traceback
4
- from typing import Optional, Dict
5
-
6
-
7
- # Configure root logger only once (idempotent)
8
- if not logging.getLogger().handlers:
9
- logging.basicConfig(
10
- level=logging.INFO,
11
- format="%(asctime)s %(levelname)s %(name)s - %(message)s",
12
- )
13
-
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")
22
-
23
-
24
- def write_log_message(message: str, level: int = logging.INFO) -> None:
25
- """로그 메시지를 쓴다."""
26
- spaced = os.getenv("LOG_SPACED", "1") != "0"
27
- suffix = "\n" if spaced else ""
28
- APPLICATION_LOGGER.log(level, f"{message}{suffix}")
29
-
30
-
31
- def handle_application_error(title: str, error: Exception, *, raise_error: bool = True, extra: Optional[Dict] = None) -> None:
32
- """예외 상황을 처리한다."""
33
- spaced = os.getenv("LOG_SPACED", "1") != "0"
34
- suffix = "\n" if spaced else ""
35
- context = f" | extra={extra}" if extra else ""
36
- APPLICATION_LOGGER.error(f"{title}: {error}{context}{suffix}")
37
- APPLICATION_LOGGER.error(traceback.format_exc())
38
- if raise_error:
39
- raise error
1
+ import logging
2
+ import os
3
+ import traceback
4
+ from typing import Optional, Dict
5
+
6
+
7
+ # Configure root logger only once (idempotent)
8
+ if not logging.getLogger().handlers:
9
+ # LOG_LEVEL 환경변수 읽기
10
+ log_level = os.getenv("LOG_LEVEL", "INFO").upper()
11
+ if log_level == "DEBUG":
12
+ level = logging.DEBUG
13
+ elif log_level == "INFO":
14
+ level = logging.INFO
15
+ elif log_level == "WARNING":
16
+ level = logging.WARNING
17
+ elif log_level == "ERROR":
18
+ level = logging.ERROR
19
+ else:
20
+ level = logging.INFO
21
+
22
+ logging.basicConfig(
23
+ level=level,
24
+ format="%(asctime)s %(levelname)s %(name)s - %(message)s",
25
+ )
26
+
27
+ LOGGER_NAME = os.getenv("LOGGER_NAME") or "processgpt"
28
+ APPLICATION_LOGGER = logging.getLogger(LOGGER_NAME)
29
+
30
+ # Application logger도 같은 레벨로 설정
31
+ log_level = os.getenv("LOG_LEVEL", "INFO").upper()
32
+ if log_level == "DEBUG":
33
+ APPLICATION_LOGGER.setLevel(logging.DEBUG)
34
+ elif log_level == "INFO":
35
+ APPLICATION_LOGGER.setLevel(logging.INFO)
36
+ elif log_level == "WARNING":
37
+ APPLICATION_LOGGER.setLevel(logging.WARNING)
38
+ elif log_level == "ERROR":
39
+ APPLICATION_LOGGER.setLevel(logging.ERROR)
40
+
41
+ # 디버그 레벨 상수 정의
42
+ DEBUG_LEVEL_NONE = 0 # 디버그 로그 없음
43
+ DEBUG_LEVEL_BASIC = 1 # 기본 디버그 로그 (INFO 레벨)
44
+ DEBUG_LEVEL_DETAILED = 2 # 상세 디버그 로그 (DEBUG 레벨)
45
+ DEBUG_LEVEL_VERBOSE = 3 # 매우 상세한 디버그 로그 (DEBUG 레벨 + 추가 정보)
46
+
47
+ # 환경변수에서 디버그 레벨 읽기
48
+ DEBUG_LEVEL = int(os.getenv("DEBUG_LEVEL", "1"))
49
+
50
+
51
+ def set_application_logger_name(name: str) -> None:
52
+ """애플리케이션 로거 이름을 런타임에 변경한다."""
53
+ global APPLICATION_LOGGER
54
+ APPLICATION_LOGGER = logging.getLogger(name or "processgpt")
55
+
56
+
57
+ def write_log_message(message: str, level: int = logging.INFO, debug_level: int = DEBUG_LEVEL_BASIC) -> None:
58
+ """로그 메시지를 쓴다. 디버그 레벨에 따라 출력 여부를 결정한다."""
59
+ # 디버그 레벨 체크
60
+ if debug_level > DEBUG_LEVEL:
61
+ return
62
+
63
+ # DEBUG 레벨 로그의 경우 로거 레벨도 확인
64
+ if level == logging.DEBUG and DEBUG_LEVEL < DEBUG_LEVEL_DETAILED:
65
+ return
66
+
67
+ spaced = os.getenv("LOG_SPACED", "1") != "0"
68
+ suffix = "\n" if spaced else ""
69
+ APPLICATION_LOGGER.log(level, f"{message}{suffix}")
70
+
71
+
72
+ def write_debug_message(message: str, debug_level: int = DEBUG_LEVEL_BASIC) -> None:
73
+ """디버그 전용 로그 메시지를 쓴다."""
74
+ write_log_message(message, logging.DEBUG, debug_level)
75
+
76
+
77
+ def write_info_message(message: str, debug_level: int = DEBUG_LEVEL_BASIC) -> None:
78
+ """정보 로그 메시지를 쓴다."""
79
+ write_log_message(message, logging.INFO, debug_level)
80
+
81
+
82
+ def set_debug_level(level: int) -> None:
83
+ """런타임에 디버그 레벨을 설정한다."""
84
+ global DEBUG_LEVEL
85
+ DEBUG_LEVEL = level
86
+ write_info_message(f"디버그 레벨이 {level}로 설정되었습니다.", DEBUG_LEVEL_BASIC)
87
+
88
+
89
+ def handle_application_error(title: str, error: Exception, *, raise_error: bool = True, extra: Optional[Dict] = None) -> None:
90
+ """예외 상황을 처리한다."""
91
+ spaced = os.getenv("LOG_SPACED", "1") != "0"
92
+ suffix = "\n" if spaced else ""
93
+ context = f" | extra={extra}" if extra else ""
94
+ APPLICATION_LOGGER.error(f"{title}: {error}{context}{suffix}")
95
+ APPLICATION_LOGGER.error(traceback.format_exc())
96
+ if raise_error:
97
+ raise error
@@ -1,146 +1,146 @@
1
- from __future__ import annotations
2
-
3
-
4
- import os
5
- import json
6
- import asyncio
7
- from typing import Any, Tuple
8
-
9
- import openai
10
- from .logger import handle_application_error, write_log_message
11
-
12
- # =============================================================================
13
- # 요약기(Summarizer)
14
- # 설명: 출력/피드백/현재 내용으로부터 OpenAI를 사용해 간단 요약을 생성한다.
15
- # =============================================================================
16
-
17
- async def summarize_async(outputs: Any, feedbacks: Any, contents: Any = None) -> Tuple[str, str]:
18
- """(output_summary, feedback_summary)를 비동기로 생성해 반환한다.
19
- 키 없음/오류 시 빈 문자열 폴백, 취소는 상위로 전파."""
20
- outputs_str = _convert_to_string(outputs).strip()
21
- feedbacks_str = _convert_to_string(feedbacks).strip()
22
- contents_str = _convert_to_string(contents).strip()
23
-
24
- output_summary = ""
25
- feedback_summary = ""
26
-
27
- if outputs_str and outputs_str not in ("[]", "{}", "[{}]"):
28
- write_log_message("요약 호출(이전결과물)")
29
- output_prompt = _create_output_summary_prompt(outputs_str)
30
- output_summary = await _call_openai_api_async(output_prompt, task_name="output")
31
-
32
- if feedbacks_str and feedbacks_str not in ("[]", "{}"):
33
- write_log_message("요약 호출(피드백)")
34
- feedback_prompt = _create_feedback_summary_prompt(feedbacks_str, contents_str)
35
- feedback_summary = await _call_openai_api_async(feedback_prompt, task_name="feedback")
36
-
37
- return output_summary or "", feedback_summary or ""
38
-
39
-
40
- # =============================================================================
41
- # 헬퍼: 문자열 변환
42
- # =============================================================================
43
-
44
- def _convert_to_string(data: Any) -> str:
45
- """임의 데이터를 안전하게 문자열로 변환한다."""
46
- if data is None:
47
- return ""
48
- if isinstance(data, str):
49
- return data
50
- try:
51
- return json.dumps(data, ensure_ascii=False)
52
- except Exception:
53
- return str(data)
54
-
55
-
56
- # =============================================================================
57
- # 헬퍼: 프롬프트 생성
58
- # =============================================================================
59
-
60
- def _create_output_summary_prompt(outputs_str: str) -> str:
61
- """결과물 요약용 사용자 프롬프트를 생성한다."""
62
- return (
63
- "다음 작업 결과를 정리해주세요:\n\n"
64
- f"{outputs_str}\n\n"
65
- "처리 방식:\n"
66
- "- 짧은 내용은 요약하지 말고 그대로 유지 (정보 손실 방지)\n"
67
- "- 긴 내용만 적절히 요약하여 핵심 정보 전달\n"
68
- "- 수치, 목차, 인물명, 물건명, 날짜, 시간 등 객관적 정보는 반드시 포함\n"
69
- "- 왜곡이나 의미 변경 금지, 원본 의미 보존\n"
70
- "- 중복만 정리하고 핵심 내용은 모두 보존\n"
71
- "- 하나의 통합된 문맥으로 작성"
72
- )
73
-
74
-
75
- def _create_feedback_summary_prompt(feedbacks_str: str, contents_str: str = "") -> str:
76
- """피드백/현재 결과물을 통합 요약하는 사용자 프롬프트를 생성한다."""
77
- feedback_section = (
78
- f"=== 피드백 내용 ===\n{feedbacks_str}" if feedbacks_str and feedbacks_str.strip() else ""
79
- )
80
- content_section = (
81
- f"=== 현재 결과물/작업 내용 ===\n{contents_str}" if contents_str and contents_str.strip() else ""
82
- )
83
- return (
84
- "다음은 사용자의 피드백과 결과물입니다. 이를 분석하여 통합된 피드백을 작성해주세요:\n\n"
85
- f"{feedback_section}\n\n{content_section}\n\n"
86
- "상황 분석 및 처리 방식:\n"
87
- "- 현재 결과물을 보고 문제/개선 필요점 판단\n"
88
- "- 최신 피드백을 최우선으로 반영\n"
89
- "- 실행 가능한 개선사항 제시\n"
90
- "- 하나의 통합된 문장으로 작성 (최대 2500자)"
91
- )
92
-
93
-
94
- # =============================================================================
95
- # 헬퍼: 시스템 프롬프트 선택
96
- # =============================================================================
97
-
98
- def _get_system_prompt(task_name: str) -> str:
99
- """작업 종류에 맞는 시스템 프롬프트를 반환한다."""
100
- if task_name == "feedback":
101
- return (
102
- "당신은 피드백 정리 전문가입니다. 최신 피드백을 우선 반영하고,"
103
- " 문맥을 연결하여 하나의 완전한 요청으로 통합해 주세요."
104
- )
105
- return (
106
- "당신은 결과물 요약 전문가입니다. 긴 내용만 요약하고,"
107
- " 수치/고유명/날짜 등 객관 정보를 보존해 주세요."
108
- )
109
-
110
-
111
- # =============================================================================
112
- # 외부 호출: OpenAI API
113
- # =============================================================================
114
-
115
- async def _call_openai_api_async(prompt: str, task_name: str) -> str:
116
- """OpenAI 비동기 API를 호출해 요약 텍스트를 생성한다."""
117
-
118
- if not (os.getenv("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY_BETA")):
119
- write_log_message("요약 비활성화: OPENAI_API_KEY 미설정")
120
- return ""
121
-
122
- client = openai.AsyncOpenAI()
123
- system_prompt = _get_system_prompt(task_name)
124
- model = os.getenv("OPENAI_SUMMARY_MODEL", "gpt-4o-mini")
125
-
126
- for attempt in range(1, 4):
127
- try:
128
- resp = await client.chat.completions.create(
129
- model=model,
130
- messages=[
131
- {"role": "system", "content": system_prompt},
132
- {"role": "user", "content": prompt},
133
- ],
134
- temperature=0.1,
135
- timeout=30.0,
136
- )
137
- return (resp.choices[0].message.content or "").strip()
138
- except asyncio.CancelledError:
139
- raise
140
- except Exception as e:
141
- if attempt < 3:
142
- handle_application_error("요약 호출 오류(재시도)", e, raise_error=False, extra={"attempt": attempt})
143
- await asyncio.sleep(0.8 * (2 ** (attempt - 1)))
144
- continue
145
- handle_application_error("요약 호출 최종 실패", e, raise_error=False)
146
- return ""
1
+ from __future__ import annotations
2
+
3
+
4
+ import os
5
+ import json
6
+ import asyncio
7
+ from typing import Any, Tuple
8
+
9
+ import openai
10
+ from .logger import handle_application_error, write_log_message
11
+
12
+ # =============================================================================
13
+ # 요약기(Summarizer)
14
+ # 설명: 출력/피드백/현재 내용으로부터 OpenAI를 사용해 간단 요약을 생성한다.
15
+ # =============================================================================
16
+
17
+ async def summarize_async(outputs: Any, feedbacks: Any, contents: Any = None) -> Tuple[str, str]:
18
+ """(output_summary, feedback_summary)를 비동기로 생성해 반환한다.
19
+ 키 없음/오류 시 빈 문자열 폴백, 취소는 상위로 전파."""
20
+ outputs_str = _convert_to_string(outputs).strip()
21
+ feedbacks_str = _convert_to_string(feedbacks).strip()
22
+ contents_str = _convert_to_string(contents).strip()
23
+
24
+ output_summary = ""
25
+ feedback_summary = ""
26
+
27
+ if outputs_str and outputs_str not in ("[]", "{}", "[{}]"):
28
+ write_log_message("요약 호출(이전결과물)")
29
+ output_prompt = _create_output_summary_prompt(outputs_str)
30
+ output_summary = await _call_openai_api_async(output_prompt, task_name="output")
31
+
32
+ if feedbacks_str and feedbacks_str not in ("[]", "{}"):
33
+ write_log_message("요약 호출(피드백)")
34
+ feedback_prompt = _create_feedback_summary_prompt(feedbacks_str, contents_str)
35
+ feedback_summary = await _call_openai_api_async(feedback_prompt, task_name="feedback")
36
+
37
+ return output_summary or "", feedback_summary or ""
38
+
39
+
40
+ # =============================================================================
41
+ # 헬퍼: 문자열 변환
42
+ # =============================================================================
43
+
44
+ def _convert_to_string(data: Any) -> str:
45
+ """임의 데이터를 안전하게 문자열로 변환한다."""
46
+ if data is None:
47
+ return ""
48
+ if isinstance(data, str):
49
+ return data
50
+ try:
51
+ return json.dumps(data, ensure_ascii=False)
52
+ except Exception:
53
+ return str(data)
54
+
55
+
56
+ # =============================================================================
57
+ # 헬퍼: 프롬프트 생성
58
+ # =============================================================================
59
+
60
+ def _create_output_summary_prompt(outputs_str: str) -> str:
61
+ """결과물 요약용 사용자 프롬프트를 생성한다."""
62
+ return (
63
+ "다음 작업 결과를 정리해주세요:\n\n"
64
+ f"{outputs_str}\n\n"
65
+ "처리 방식:\n"
66
+ "- 짧은 내용은 요약하지 말고 그대로 유지 (정보 손실 방지)\n"
67
+ "- 긴 내용만 적절히 요약하여 핵심 정보 전달\n"
68
+ "- 수치, 목차, 인물명, 물건명, 날짜, 시간 등 객관적 정보는 반드시 포함\n"
69
+ "- 왜곡이나 의미 변경 금지, 원본 의미 보존\n"
70
+ "- 중복만 정리하고 핵심 내용은 모두 보존\n"
71
+ "- 하나의 통합된 문맥으로 작성"
72
+ )
73
+
74
+
75
+ def _create_feedback_summary_prompt(feedbacks_str: str, contents_str: str = "") -> str:
76
+ """피드백/현재 결과물을 통합 요약하는 사용자 프롬프트를 생성한다."""
77
+ feedback_section = (
78
+ f"=== 피드백 내용 ===\n{feedbacks_str}" if feedbacks_str and feedbacks_str.strip() else ""
79
+ )
80
+ content_section = (
81
+ f"=== 현재 결과물/작업 내용 ===\n{contents_str}" if contents_str and contents_str.strip() else ""
82
+ )
83
+ return (
84
+ "다음은 사용자의 피드백과 결과물입니다. 이를 분석하여 통합된 피드백을 작성해주세요:\n\n"
85
+ f"{feedback_section}\n\n{content_section}\n\n"
86
+ "상황 분석 및 처리 방식:\n"
87
+ "- 현재 결과물을 보고 문제/개선 필요점 판단\n"
88
+ "- 최신 피드백을 최우선으로 반영\n"
89
+ "- 실행 가능한 개선사항 제시\n"
90
+ "- 하나의 통합된 문장으로 작성 (최대 2500자)"
91
+ )
92
+
93
+
94
+ # =============================================================================
95
+ # 헬퍼: 시스템 프롬프트 선택
96
+ # =============================================================================
97
+
98
+ def _get_system_prompt(task_name: str) -> str:
99
+ """작업 종류에 맞는 시스템 프롬프트를 반환한다."""
100
+ if task_name == "feedback":
101
+ return (
102
+ "당신은 피드백 정리 전문가입니다. 최신 피드백을 우선 반영하고,"
103
+ " 문맥을 연결하여 하나의 완전한 요청으로 통합해 주세요."
104
+ )
105
+ return (
106
+ "당신은 결과물 요약 전문가입니다. 긴 내용만 요약하고,"
107
+ " 수치/고유명/날짜 등 객관 정보를 보존해 주세요."
108
+ )
109
+
110
+
111
+ # =============================================================================
112
+ # 외부 호출: OpenAI API
113
+ # =============================================================================
114
+
115
+ async def _call_openai_api_async(prompt: str, task_name: str) -> str:
116
+ """OpenAI 비동기 API를 호출해 요약 텍스트를 생성한다."""
117
+
118
+ if not (os.getenv("OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY_BETA")):
119
+ write_log_message("요약 비활성화: OPENAI_API_KEY 미설정")
120
+ return ""
121
+
122
+ client = openai.AsyncOpenAI()
123
+ system_prompt = _get_system_prompt(task_name)
124
+ model = os.getenv("OPENAI_SUMMARY_MODEL", "gpt-4o-mini")
125
+
126
+ for attempt in range(1, 4):
127
+ try:
128
+ resp = await client.chat.completions.create(
129
+ model=model,
130
+ messages=[
131
+ {"role": "system", "content": system_prompt},
132
+ {"role": "user", "content": prompt},
133
+ ],
134
+ temperature=0.1,
135
+ timeout=30.0,
136
+ )
137
+ return (resp.choices[0].message.content or "").strip()
138
+ except asyncio.CancelledError:
139
+ raise
140
+ except Exception as e:
141
+ if attempt < 3:
142
+ handle_application_error("요약 호출 오류(재시도)", e, raise_error=False, extra={"attempt": attempt})
143
+ await asyncio.sleep(0.8 * (2 ** (attempt - 1)))
144
+ continue
145
+ handle_application_error("요약 호출 최종 실패", e, raise_error=False)
146
+ return ""