mirror of
https://github.com/j93es/browser-use-oauth.git
synced 2026-06-04 05:21:52 +09:00
feat: Prompt 구조 개선
This commit is contained in:
parent
d8ec21c61b
commit
70e8bdbbde
12 changed files with 130 additions and 66 deletions
25
README.md
25
README.md
|
|
@ -116,23 +116,30 @@ uv run run.py 1 100 --skh
|
||||||
|
|
||||||
## 1. 파일 생성
|
## 1. 파일 생성
|
||||||
|
|
||||||
`lib/llm/prompt` 폴더로
|
`lib/llm/prompt` 폴더에서 fallback 폴더를 복사하여
|
||||||
|
|
||||||

|
원하는 프로바이더를 추가해줍니다. `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`
|
||||||
|
|
||||||

|
## 3. model.py
|
||||||
|
|
||||||
Prompt에서 추가한 파일을 __init__.py에서 import합니다.
|
응답할 때 원하는 리턴 값을 `dict`로 받습니다.
|
||||||
|
|
||||||
## 3. 파일 수정
|
## 4. \_\_init\_\_.py 수정
|
||||||
|

|
||||||
|
|
||||||
생성한 파일에서 프롬프트를 수정합니다.
|
추가한 prompt에 따라 import합니다.
|
||||||
|
|
||||||
|
## 5. 사용 방법
|
||||||
|
```py
|
||||||
|
from lib.llm.prompt.fallback import prompt, model
|
||||||
|
```
|
||||||
|
|
||||||
# 참고하면 좋을만한 것
|
# 참고하면 좋을만한 것
|
||||||
|
|
||||||
|
|
|
||||||
BIN
docs/guide.png
BIN
docs/guide.png
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 MiB |
BIN
docs/guide_0.png
Normal file
BIN
docs/guide_0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 이름을 포함해 주세요.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
2
src/lib/llm/prompt/fallback/__init__.py
Normal file
2
src/lib/llm/prompt/fallback/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
from lib.llm.prompt.fallback.prompt import prompt
|
||||||
|
from lib.llm.prompt.fallback.model import model
|
||||||
6
src/lib/llm/prompt/fallback/model.py
Normal file
6
src/lib/llm/prompt/fallback/model.py
Normal 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
|
||||||
|
|
@ -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 로그인은 반드시 실행** (가능한 버튼은 모두 클릭)
|
||||||
2
src/lib/llm/prompt/get_oauth/__init__.py
Normal file
2
src/lib/llm/prompt/get_oauth/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
from lib.llm.prompt.get_oauth.prompt import prompt
|
||||||
|
from lib.llm.prompt.get_oauth.model import model
|
||||||
6
src/lib/llm/prompt/get_oauth/model.py
Normal file
6
src/lib/llm/prompt/get_oauth/model.py
Normal 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
|
||||||
61
src/lib/llm/prompt/get_oauth/prompt.py
Normal file
61
src/lib/llm/prompt/get_oauth/prompt.py
Normal 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.
|
||||||
|
"""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue