feat: Prompt 구조 개선

This commit is contained in:
암냥 2025-06-27 20:28:12 +09:00
commit 70e8bdbbde
12 changed files with 130 additions and 66 deletions

View file

@ -116,23 +116,30 @@ uv run run.py 1 100 --skh
## 1. 파일 생성 ## 1. 파일 생성
`lib/llm/prompt` 폴더 `lib/llm/prompt` 폴더에서 fallback 폴더를 복사하여
![](./docs/list.png) 원하는 프로바이더를 추가해줍니다. `ex) lib/llm/prompt/Google/`
fallback.py를 복사하여 ## 2. prompt.py 수정
원하는 프로바이더를 추가해줍니다. `ex) lib/llm/prompt/Google.py` Prompt에서 추가한 파일을 prompt.py에서 수정합니다.
## 2. __init__.py 수정 만약 로그인 정보를 넣고 싶다면 Sensitive
`Log into example.com as user x_username with password x_password`
![](./docs/guide.png) ## 3. model.py
Prompt에서 추가한 파일을 __init__.py에서 import합니다. 응답할 때 원하는 리턴 값을 `dict`로 받습니다.
## 3. 파일 수정 ## 4. \_\_init\_\_.py 수정
![alt text](./docs/guide_0.png)
생성한 파일에서 프롬프트를 수정합니다. 추가한 prompt에 따라 import합니다.
## 5. 사용 방법
```py
from lib.llm.prompt.fallback import prompt, model
```
# 참고하면 좋을만한 것 # 참고하면 좋을만한 것

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

BIN
docs/guide_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

View file

@ -15,7 +15,6 @@ from lib.utils import (
config, config,
) )
from lib.llm import CreateChatGoogleGenerativeAI, get_prompt from lib.llm import CreateChatGoogleGenerativeAI, get_prompt
import lib.browser_use.model as model
# Exponential backoff settings # Exponential backoff settings
INITIAL_BACKOFF = int(os.getenv("INITIAL_BACKOFF", "60")) # seconds INITIAL_BACKOFF = int(os.getenv("INITIAL_BACKOFF", "60")) # seconds
@ -77,6 +76,7 @@ async def extract_oauth_list(url: str):
"""첫 번째 Agent: 로그인 페이지를 찾고 OAuth 리스트만 추출""" """첫 번째 Agent: 로그인 페이지를 찾고 OAuth 리스트만 추출"""
target_url = url if url.startswith("http") else f"https://{url}" target_url = url if url.startswith("http") else f"https://{url}"
print(f"🔎 OAuth 리스트 추출 시작: {target_url}") print(f"🔎 OAuth 리스트 추출 시작: {target_url}")
prompt, model = get_prompt("auth")
agent_config = { agent_config = {
"url": target_url, "url": target_url,
@ -97,10 +97,10 @@ async def extract_oauth_list(url: str):
else None else None
), ),
"controller": Controller( "controller": Controller(
output_model=model.OAuthList, output_model=model if not isinstance(model, str) else None,
exclude_actions=["search_google", "unknown_action", "unkown"], exclude_actions=["search_google", "unknown_action", "unkown"],
), ),
"extend_planner_system_message": get_prompt("auth"), "extend_planner_system_message": prompt,
} }
} }
@ -117,7 +117,12 @@ async def extract_oauth_list(url: str):
try: try:
data = json.loads(final_result) data = json.loads(final_result)
oauth_providers = data.get("oauth_providers", []) oauth_providers = data.get("oauth_providers", [])
return [model.OAuth(provider=provider) for provider in oauth_providers] if not oauth_providers:
print("❌ OAuth 제공자가 없습니다.")
logger(f"{url} - OAuth 제공자 없음: {final_result}")
return []
print(f"✅ OAuth 제공자 추출 완료: {oauth_providers}")
return oauth_providers
except (json.JSONDecodeError, KeyError) as e: except (json.JSONDecodeError, KeyError) as e:
print(f"❌ 결과 파싱 실패: {e}") print(f"❌ 결과 파싱 실패: {e}")
logger(f"{url} 결과 파싱 실패: {final_result}") logger(f"{url} 결과 파싱 실패: {final_result}")
@ -129,6 +134,8 @@ async def test_oauth_login(url: str, oauth_provider: str):
target_url = url if url.startswith("http") else f"https://{url}" target_url = url if url.startswith("http") else f"https://{url}"
print(f"🔐 {oauth_provider} 로그인 시작: {target_url}") print(f"🔐 {oauth_provider} 로그인 시작: {target_url}")
prompt, model = get_prompt(oauth_provider)
agent_config = { agent_config = {
"url": target_url, "url": target_url,
"log_context": f"{oauth_provider} 로그인", "log_context": f"{oauth_provider} 로그인",
@ -149,9 +156,10 @@ async def test_oauth_login(url: str, oauth_provider: str):
else None else None
), ),
"controller": Controller( "controller": Controller(
output_model=model if not isinstance(model, str) else None,
exclude_actions=["search_google", "unknown_action", "unkown"], exclude_actions=["search_google", "unknown_action", "unkown"],
), ),
"extend_planner_system_message": get_prompt(oauth_provider), "extend_planner_system_message": prompt,
} }
} }

View file

@ -1,18 +1,17 @@
# why this is isn't index from typing import Union, Type
# 이 파일을 __init__.py로 만든 이유는 from pydantic import BaseModel
# 굳이 이 짧은 코드를 파일을 하나 더 만드는게 코드의 가독성을 떨어뜨린다고 판단했기 때문입니다.
def get_prompt(type:str) -> str: def get_prompt(type: str) -> tuple[str, Type[BaseModel]] | str:
""" """
Prompt를 반환합니다. Prompt를 반환합니다.
:param type: 'auth' {Auth List} 또는 'google' {OAuth Provider}, 'meta' {OAuth Provider} 지정합니다. :param type: 'auth' {Auth List} 또는 'google' {OAuth Provider}, 'meta' {OAuth Provider} 지정합니다.
:return: 해당하는 프롬프트 문자열 :return: 해당하는 프롬프트 문자열 또는 (프롬프트, 모델) 튜플
""" """
if type.lower() == "auth": if type.lower() == "auth":
from lib.llm.prompt.auth_list import extract_oauth_list_prompt from lib.llm.prompt.get_oauth import prompt, model
return extract_oauth_list_prompt return prompt, model
else: else:
from lib.llm.prompt.fallback import extend_planner_system_message from lib.llm.prompt.fallback import model, prompt
return extend_planner_system_message return prompt, model

View file

@ -1,41 +0,0 @@
# @file purpose: This file contains the prompt for extracting a list of OAuth providers from a web page.
# OAuth 리스트 추출용 프롬프트 (클릭하지 않고 단순 식별만)
extract_oauth_list_prompt = f"""
🎯 목적: 주어진 초기 URL 내에서 **OAuth 로그인 Provider** 찾아 아래 형식의 JSON으로 정리합니다.
📌 작업 목표:
- Google, GitHub, Discord, Facebook, Apple, Microsoft, Twitter, LinkedIn **OAuth 인증을 사용하는 외부 로그인 링크**에서 Provider 이름만 모두 수집합니다.
- 로그인 버튼, 링크 클릭 등을 통해 탐색을 진행할 있습니다.
- **같은 provider가 여러 나와도 하나만 저장**합니다.
🛑 제한 사항:
- 로그인 입력창이나 이메일/비밀번호 입력 방식은 제외합니다.
- 검색 엔진, 사이트 외부 탐색은 금지합니다.
- URL 추측이나 직접 입력은 금지합니다.
- OAuth가 없는 경우 배열 `[]` 반환합니다.
- OAuth가 아닌 일반 로그인은 무시합니다.
🔍 탐색 방법:
1. 초기 URL에 접속하여 **클라이언트용 로그인 페이지** 진입합니다.
2. 페이지가 정상적으로 로드되었다고 가정합니다.
3. 'Continue with X', 'Continue with Google'... 등의 버튼이나 링크를 식별합니다.
🧾 출력 형식 (예시):
```json
{{
"oauth_providers": [
"Google",
"GitHub",
"Discord"
]
}}
```
📌 주의:
결과가 없는 경우 배열 `[]` 반환합니다.
정확한 provider 이름을 포함해 주세요.
"""

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.fallback.prompt import prompt
from lib.llm.prompt.fallback.model import model

View file

@ -0,0 +1,6 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
status: str | None = None # "success", "mfa_required", "google_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
final_url: str | None = None

View file

@ -1,5 +1,5 @@
# Extended planner prompt # Extended planner prompt
extend_planner_system_message = f""" prompt = f"""
🎯 목적: 자동화를 위한 **SSO 로그인 리디렉션 URL 수집** 🎯 목적: 자동화를 위한 **SSO 로그인 리디렉션 URL 수집**
📌 주의사항 (전제 조건) 📌 주의사항 (전제 조건)
@ -96,6 +96,20 @@ chrome://settings/clearBrowserData에 들어가서 삭제해주세요.
--- ---
최종 반환:
Return the result in the following format only:
```json
{{
"msg": "Google login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "google_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
---
## 📎 중요 규칙 요약 ## 📎 중요 규칙 요약
* **모든 SSO 로그인은 반드시 실행** (가능한 버튼은 모두 클릭) * **모든 SSO 로그인은 반드시 실행** (가능한 버튼은 모두 클릭)

View file

@ -0,0 +1,2 @@
from lib.llm.prompt.get_oauth.prompt import prompt
from lib.llm.prompt.get_oauth.model import model

View file

@ -0,0 +1,6 @@
from pydantic import BaseModel
class model(BaseModel):
msg: str | None = None
url: str | None = None
sso_list: list[str] = [] # List of SSO providers found on the login page

View file

@ -0,0 +1,61 @@
prompt = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL. Follow the steps below strictly and return results only in the specified format.
You are NOT allowed to navigate to URLs that are not directly discoverable within the initial domain. Do NOT use search engines or guess external login URLs.
0. INITIAL BLOCK CHECK
- If the browser is blocked when trying to access the page due to firewall, CAPTCHA, regional restrictions, or other access denials immediately terminate the process and return the following JSON:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Do NOT proceed to further steps in this case.
1. LOGIN PAGE NAVIGATION
- Navigate only to a **client-side (non-enterprise)** login page within the provided domain.
- Do NOT rely on external tools, search engines, or links not directly found on the site.
- If a consent popup (e.g. for privacy/cookies) appears, you MUST dismiss or close it before proceeding.
- Since step 0 confirmed access, assume the page now loads properly.
2. SSO BUTTON IDENTIFICATION
- On the login page, look for the following social login (SSO) buttons:
- Google, GitHub, Facebook, LinkedIn, Microsoft, Naver, Slack, Etc.
- Proceed only if it is clearly an **actual SSO button**.
- Exclude the following:
- Passkey-related buttons
- Username/password fields
- Email-based login
- Non-OAuth methods such as certificate or phone verification
3. RETURN FORMAT
- If the login page is successfully found, return:
```json
{
"msg": "Login page found",
"url": "https://example.com/login",
"sso_list": ["Google", "GitHub"]
}
```
- If the login page cannot be found, return:
```json
{
"msg": "Login page not found",
"url": "",
"sso_list": []
}
```
- If blocked (as in step 0), return:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""