chore: add pretty_json utility and sibling comment awareness to bot realism mechanics

This commit is contained in:
2026-06-12 06:30:17 +00:00
parent ce3cae1433
commit c1f314f2c6
7 changed files with 112 additions and 8 deletions
+49 -1
View File
@@ -18,6 +18,7 @@ from devplacepy.services.bot.config import (
FEED_TOPICS,
GIST_LANGUAGES,
HANDLE_SUFFIX_WORDS,
MAX_SIBLING_COMMENTS,
PERSONAS,
PROJECT_STATUSES,
PROJECT_TYPES,
@@ -26,6 +27,7 @@ from devplacepy.services.bot.config import (
SEARCH_TERMS,
SESSION_COMMENT_CAP_MAX,
SESSION_COMMENT_CAP_MIN,
SIBLING_SNIPPET_LEN,
USER_AGENTS,
VIEWPORTS,
persona_article_score,
@@ -829,6 +831,39 @@ class DevPlaceBot:
self.state.own_post_urls.append(curl)
return is_own
async def _existing_comments(self) -> list[str]:
bodies = self.b.page.locator(".comment-body")
try:
count = await bodies.count()
except Exception:
return []
if count == 0:
return []
own = self.state.username.lower()
collected: list[str] = []
start = max(0, count - MAX_SIBLING_COMMENTS)
for idx in range(start, count):
node = bodies.nth(idx)
try:
text = (await node.locator(".comment-text").first.inner_text()).strip()
except Exception:
continue
if not text:
continue
author = ""
try:
link = node.locator("a[href*='/profile/']").first
if await link.count() > 0:
href = await link.get_attribute("href") or ""
author = href.split("/profile/")[-1].split("?")[0].split("/")[0]
except Exception:
author = ""
if author and author.lower() == own:
continue
snippet = text[:SIBLING_SNIPPET_LEN]
collected.append(f"{author}: {snippet}" if author else snippet)
return collected
async def _comment_on_post(self) -> bool:
b = self.b
curl = await b.url()
@@ -891,6 +926,10 @@ class DevPlaceBot:
mention_target = random.choice(known)
style = self.llm.pick_comment_style()
siblings = await self._existing_comments()
siblings_block = "\n".join(f"- {c}" for c in siblings)
siblings_text = " ".join(siblings)
comment = ""
for attempt in range(2):
candidate = await self._generate(
@@ -900,11 +939,17 @@ class DevPlaceBot:
mention_target,
"",
style,
siblings_block,
)
if not candidate:
continue
verdict = await self._generate(
self.llm.quality_check, "comment", candidate, post_body[:1500], style
self.llm.quality_check,
"comment",
candidate,
post_body[:1500],
style,
siblings_text,
)
if verdict is None or verdict[0]:
comment = candidate
@@ -1039,6 +1084,8 @@ class DevPlaceBot:
logger.debug("reply-btn click failed: %s", e)
continue
post_body = await b.text(".post-content") or await b.text("article") or ""
siblings = [c for c in await self._existing_comments() if text[:60] not in c]
siblings_block = "\n".join(f"- {c}" for c in siblings)
reply = await self._generate(
self.llm.generate_comment,
post_body[:1200],
@@ -1046,6 +1093,7 @@ class DevPlaceBot:
mentioner,
text,
self.llm.pick_comment_style(),
siblings_block,
)
if not reply:
self._log("Reply: generation failed/empty")
+3
View File
@@ -134,6 +134,9 @@ MIN_COMMENT_LEN = 40
MIN_SHORT_COMMENT_LEN = 12
MIN_POST_LEN = 120
RESTATEMENT_OVERLAP_THRESHOLD = 0.6
SIBLING_OVERLAP_THRESHOLD = 0.55
MAX_SIBLING_COMMENTS = 8
SIBLING_SNIPPET_LEN = 240
GENERIC_COMMENT_PHRASES = [
"great point",
+23 -3
View File
@@ -275,6 +275,7 @@ class LLMClient:
mention_target: str = "",
parent_context: str = "",
style: str = "",
existing_comments: str = "",
) -> str:
extras = {
"enthusiastic_junior": "Be excited. Use **bold** for agreement. Short replies.",
@@ -313,16 +314,29 @@ class LLMClient:
ctx = ""
if parent_context:
ctx = f"\n\nReplying to this comment:\n{parent_context[:800]}"
others = ""
distinct_rule = ""
if existing_comments:
others = (
"\n\nComments already posted by others on this thread:\n"
f"{existing_comments[:1600]}"
)
distinct_rule = (
" Other people already commented (listed below). Contribute a point "
"none of them made: a different angle on the topic, a caveat they "
"missed, or respectful disagreement with one of them by name. Do not "
"repeat any opinion, framing, example, or question already raised."
)
system = (
f"You are a dev replying to a post. {length_rule}{mention_rule} "
"No em dashes. Reference a specific detail from the post. "
"Do not open with or rely on generic filler like 'great point', 'I agree', 'nice', "
"'thanks for sharing', or 'interesting'. Add something real: a concrete experience, "
"a caveat, a counterexample, respectful disagreement, or a pointed question. "
"Never just paraphrase or agree blandly."
f"Never just paraphrase or agree blandly.{distinct_rule}"
)
reply = self.clean(
self._call(system, f"Post:\n{post_snippet[:1500]}{ctx}").strip(),
self._call(system, f"Post:\n{post_snippet[:1500]}{ctx}{others}").strip(),
preserve_md=preserve,
)
return reply[:2000]
@@ -340,13 +354,14 @@ class LLMClient:
return hits / len(candidate)
def quality_check(
self, kind: str, text: str, context: str = "", style: str = ""
self, kind: str, text: str, context: str = "", style: str = "", siblings: str = ""
) -> tuple[bool, str]:
from devplacepy.services.bot.config import (
MIN_COMMENT_LEN,
MIN_SHORT_COMMENT_LEN,
MIN_POST_LEN,
RESTATEMENT_OVERLAP_THRESHOLD,
SIBLING_OVERLAP_THRESHOLD,
GENERIC_COMMENT_PHRASES,
)
@@ -371,6 +386,11 @@ class LLMClient:
and self._overlap_ratio(stripped, context) > RESTATEMENT_OVERLAP_THRESHOLD
):
return False, "restates the source"
if (
siblings
and self._overlap_ratio(stripped, siblings) > SIBLING_OVERLAP_THRESHOLD
):
return False, "echoes another comment"
verdict = self._call(
"You are a strict content quality reviewer for a developer community. "
"Reject text that is generic, low-effort, pure filler, or merely summarizes its "