my prefect server setup prefect-metrics.waow.tech
python orchestration
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

resolve bsky profiles in compact flow to prevent name hallucination

the compaction LLM was guessing user names from context — it hallucinated
"zoë" for a user named nate. now fetches display name and bio from the
public bluesky API and includes them in the synthesis prompt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 50a09258 ee534ef0

+36 -1
+36 -1
flows/compact.py
··· 13 13 from datetime import datetime, timedelta, timezone 14 14 15 15 import duckdb 16 + import httpx 16 17 import turbopuffer 17 18 from openai import OpenAI 18 19 from pydantic_ai import Agent ··· 35 36 uncertainty. if the relationship is thin, say so — a thin summary beats 36 37 a fabricated one. include concrete details (projects, interests, topics) 37 38 not just vibes. 39 + 40 + IMPORTANT: use ONLY facts present in the provided data. the user's 41 + bluesky profile (handle, display name, bio) is included — use that for 42 + their name and identity. never guess or infer names. 38 43 """ 39 44 40 45 ··· 120 125 return "\n\n".join(lines) 121 126 122 127 128 + @task 129 + def resolve_bsky_profile(handle: str) -> dict | None: 130 + """Fetch display name and bio from the public Bluesky API.""" 131 + try: 132 + resp = httpx.get( 133 + "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile", 134 + params={"actor": handle}, 135 + timeout=10, 136 + ) 137 + resp.raise_for_status() 138 + data = resp.json() 139 + return { 140 + "handle": data.get("handle", handle), 141 + "display_name": data.get("displayName", ""), 142 + "bio": data.get("description", ""), 143 + } 144 + except Exception: 145 + return None 146 + 147 + 123 148 def _format_stats(profile: dict) -> str: 124 149 tags = ", ".join(profile.get("top_tags") or []) 125 150 return ( ··· 144 169 observations_text: str, 145 170 interactions_text: str, 146 171 api_key: str, 172 + bsky_profile: dict | None = None, 147 173 ) -> str: 148 174 """LLM synthesis of a relationship summary. Cached by observations hash.""" 149 175 model = AnthropicModel("claude-haiku-4-5", provider=AnthropicProvider(api_key=api_key)) 150 176 agent = Agent(model, system_prompt=SYSTEM_PROMPT, name="phi-compactor") 151 177 178 + profile_section = f"handle: @{handle}\n" 179 + if bsky_profile: 180 + if bsky_profile.get("display_name"): 181 + profile_section += f"display name: {bsky_profile['display_name']}\n" 182 + if bsky_profile.get("bio"): 183 + profile_section += f"bio: {bsky_profile['bio']}\n" 184 + 152 185 prompt = ( 153 - f"user: @{handle}\n" 186 + f"user profile:\n{profile_section}\n" 154 187 f"stats: {stats_text}\n\n" 155 188 f"observations:\n{observations_text}\n\n" 156 189 f"recent interactions:\n{interactions_text}" ··· 243 276 244 277 for profile in profiles: 245 278 handle = profile["handle"] 279 + bsky_profile = resolve_bsky_profile(handle) 246 280 obs_text = load_user_observations(snap_path, handle) 247 281 ix_text = load_user_interactions(snap_path, handle) 248 282 stats_text = _format_stats(profile) 249 283 250 284 summary = await synthesize_summary( 251 285 handle, stats_text, obs_text, ix_text, anthropic_key, 286 + bsky_profile=bsky_profile, 252 287 ) 253 288 write_summary_to_turbopuffer(tpuf_key, openai_key, handle, summary) 254 289 logger.info(f"@{handle}: compacted {profile['observation_count']} observations")