-
Notifications
You must be signed in to change notification settings - Fork 1
feat(doubao): capture conversation_url and classify captcha blocks #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -7,8 +7,18 @@ | |||||
| from backend.channels.base import AbstractChannel, Capabilities, ChannelResult | ||||||
| from backend.channels.registry import register_channel | ||||||
|
|
||||||
| _URL_RE = re.compile(r"https?://[^\s<>\]\[\](){}\"']+", re.IGNORECASE) | ||||||
| _URL_RE = re.compile(r"https?://[^\s<>\[\](){}'\"]+", re.IGNORECASE) | ||||||
| _TRAILING_URL_PUNCTUATION = ".,;:!?\uff0c\u3002\uff1b\uff1a\uff01\uff1f" | ||||||
| #: OpenCLI adapter reports a captcha wall this way (verified on opencli 1.8.6). | ||||||
| _CAPTCHA_MARKERS = ( | ||||||
| "verification challenge", | ||||||
| "captcha", | ||||||
| "blocked the request", | ||||||
| "人机验证", | ||||||
| "验证码", | ||||||
| ) | ||||||
|
|
||||||
|
|
||||||
| def _citations(text: str) -> list[dict[str, str]]: | ||||||
| """Extract and de-duplicate URLs while preserving the answer's order.""" | ||||||
| seen: set[str] = set() | ||||||
|
|
@@ -37,6 +47,25 @@ def _answer(rows: list[dict[str, Any]]) -> str: | |||||
| ).strip() | ||||||
|
|
||||||
|
|
||||||
| def _conversation_url(stdout: str) -> str: | ||||||
| """Extract https://www.doubao.com/chat/<id> from `doubao status -f json` output.""" | ||||||
| try: | ||||||
| rows = _parse_opencli_rows(stdout) | ||||||
| except Exception: | ||||||
| return "" | ||||||
| for row in rows: | ||||||
| url = str(row.get("Url", row.get("url", "")) or "").strip() | ||||||
| if "/chat/" in url: | ||||||
| return url | ||||||
| return "" | ||||||
|
|
||||||
|
|
||||||
| def _is_captcha_block(stderr: str, stdout: str) -> bool: | ||||||
| """True when the adapter reports a captcha/verification wall.""" | ||||||
| text = f"{stderr} {stdout}".lower() | ||||||
| return any(marker in text for marker in _CAPTCHA_MARKERS) | ||||||
|
|
||||||
|
|
||||||
| async def _run_doubao_command(command: list[str]) -> tuple[int, str, str]: | ||||||
| """Late import avoids the channel registry's legacy OpenCLI import cycle.""" | ||||||
| from backend.channels.opencli_channel import _run_opencli | ||||||
|
|
@@ -102,8 +131,12 @@ async def collect(self, config: dict[str, Any], parameters: dict[str, Any]) -> C | |||||
| ) | ||||||
|
|
||||||
| if returncode: | ||||||
| # Classify captcha walls so the runner can apply a human-in-the-loop | ||||||
| # or cooldown-retry policy instead of treating it as a permanent failure. | ||||||
| error_type = "captcha_challenge" if _is_captcha_block(stderr, stdout) else None | ||||||
| return ChannelResult.fail( | ||||||
| f"opencli doubao ask exited with code {returncode}: {stderr[:500]}" | ||||||
| f"opencli doubao ask exited with code {returncode}: {stderr[:500]}", | ||||||
| error_type=error_type, | ||||||
| ) | ||||||
| try: | ||||||
| answer = _answer(_parse_opencli_rows(stdout)) | ||||||
|
|
@@ -114,6 +147,28 @@ async def collect(self, config: dict[str, Any], parameters: dict[str, Any]) -> C | |||||
| if not answer: | ||||||
| return ChannelResult.fail("Doubao returned no assistant text") | ||||||
|
|
||||||
| # Best-effort conversation URL: `doubao status -f json` exposes the | ||||||
| # active chat id (https://www.doubao.com/chat/<id>). This is a | ||||||
| # read-only query against the same browser session; a failure here | ||||||
| # must not fail the collect — the answer is already in hand. | ||||||
| conversation_url = "" | ||||||
| if config.get("capture_conversation_url", True): | ||||||
| status_command = [ | ||||||
| _opencli_binary(), | ||||||
| "doubao", | ||||||
| "status", | ||||||
| "-f", | ||||||
| "json", | ||||||
| "--site-session", | ||||||
| str(config.get("site_session", "ephemeral")), | ||||||
| ] | ||||||
| try: | ||||||
| rc, so, se = await _run_doubao_command(status_command) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Remove the unused status-error binding. Line 166 binds Proposed fix- rc, so, se = await _run_doubao_command(status_command)
+ rc, so, _ = await _run_doubao_command(status_command)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.16.1)[warning] 166-166: Unpacked variable Prefix it with an underscore or any other dummy variable pattern (RUF059) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||
| if rc == 0: | ||||||
| conversation_url = _conversation_url(so) | ||||||
| except Exception: | ||||||
| conversation_url = "" | ||||||
|
|
||||||
| citations = _citations(answer) if extract_citations else [] | ||||||
| return ChannelResult.ok( | ||||||
| [ | ||||||
|
|
@@ -122,6 +177,7 @@ async def collect(self, config: dict[str, Any], parameters: dict[str, Any]) -> C | |||||
| "content": answer, | ||||||
| "author": "doubao", | ||||||
| "question": question, | ||||||
| "conversation_url": conversation_url, | ||||||
| "citations": citations, | ||||||
| "citation_count": len(citations), | ||||||
| "citation_capture": ( | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate the extracted conversation URL.
The predicate accepts
httpURLs, non-Doubao hosts, and/chat/with no conversation ID. The method then stores these values inconversation_url, although its contract requires an HTTPSwww.doubao.com/chat/<id>URL.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents