Fix output order and add Reddit error handling
- SKILL.md: Move "What I learned" BEFORE "Research Complete" stats - Add error tracking to Report schema (reddit_error, x_error fields) - Wrap OpenAI API calls in try/catch with clear error messages - Show explicit error or "no results" messages in compact output - Fix false positive error detection for null error fields Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -108,20 +108,15 @@ Read the research output and become an **expert**. Identify:
|
|||||||
|
|
||||||
## THEN: Show Summary + Invite Vision
|
## THEN: Show Summary + Invite Vision
|
||||||
|
|
||||||
Display in this EXACT order (so stats are visible at bottom of terminal):
|
**CRITICAL ORDER**: Display sections in this EXACT sequence (insights FIRST, stats LAST):
|
||||||
|
|
||||||
```
|
```
|
||||||
**Key patterns discovered:**
|
---
|
||||||
1. [Pattern 1] - [one-line insight]
|
What I learned:
|
||||||
2. [Pattern 2] - [one-line insight]
|
|
||||||
3. [Pattern 3] - [one-line insight]
|
|
||||||
4. [Pattern 4] - [one-line insight]
|
|
||||||
5. [Pattern 5] - [one-line insight]
|
|
||||||
|
|
||||||
I'm now an expert in {TOPIC}.
|
[2-4 sentences synthesizing the key insight from your research. What's the secret? What pattern emerged? What do experts do differently? Write this as a mini-expert briefing, not a list.]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
📊 Research Complete
|
📊 Research Complete
|
||||||
|
|
||||||
Analyzed {total_sources} sources from the last 30 days
|
Analyzed {total_sources} sources from the last 30 days
|
||||||
@@ -129,6 +124,7 @@ Analyzed {total_sources} sources from the last 30 days
|
|||||||
├─ X: {n} posts │ {sum} likes │ {sum} reposts
|
├─ X: {n} posts │ {sum} likes │ {sum} reposts
|
||||||
└─ Top voices: r/{sub1}, r/{sub2}, @{handle1}, @{handle2}
|
└─ Top voices: r/{sub1}, r/{sub2}, @{handle1}, @{handle2}
|
||||||
|
|
||||||
|
---
|
||||||
Share your vision for what you want to create and I'll write a thoughtful prompt you can copy-paste directly into {TARGET_TOOL}.
|
Share your vision for what you want to create and I'll write a thoughtful prompt you can copy-paste directly into {TARGET_TOOL}.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+19
-3
@@ -29,6 +29,7 @@ from lib import (
|
|||||||
dates,
|
dates,
|
||||||
dedupe,
|
dedupe,
|
||||||
env,
|
env,
|
||||||
|
http,
|
||||||
models,
|
models,
|
||||||
normalize,
|
normalize,
|
||||||
openai_reddit,
|
openai_reddit,
|
||||||
@@ -62,25 +63,38 @@ def run_research(
|
|||||||
"""Run the research pipeline.
|
"""Run the research pipeline.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched)
|
Tuple of (reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error)
|
||||||
"""
|
"""
|
||||||
reddit_items = []
|
reddit_items = []
|
||||||
x_items = []
|
x_items = []
|
||||||
raw_openai = None
|
raw_openai = None
|
||||||
raw_xai = None
|
raw_xai = None
|
||||||
raw_reddit_enriched = []
|
raw_reddit_enriched = []
|
||||||
|
reddit_error = None
|
||||||
|
x_error = None
|
||||||
|
|
||||||
# Reddit search via OpenAI
|
# Reddit search via OpenAI
|
||||||
if sources in ("both", "reddit"):
|
if sources in ("both", "reddit"):
|
||||||
if mock:
|
if mock:
|
||||||
raw_openai = load_fixture("openai_sample.json")
|
raw_openai = load_fixture("openai_sample.json")
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
raw_openai = openai_reddit.search_reddit(
|
raw_openai = openai_reddit.search_reddit(
|
||||||
config["OPENAI_API_KEY"],
|
config["OPENAI_API_KEY"],
|
||||||
selected_models["openai"],
|
selected_models["openai"],
|
||||||
topic,
|
topic,
|
||||||
depth=depth,
|
depth=depth,
|
||||||
)
|
)
|
||||||
|
except http.HTTPError as e:
|
||||||
|
print(f"[REDDIT ERROR] OpenAI API request failed: {e}", flush=True)
|
||||||
|
if e.body:
|
||||||
|
print(f"[REDDIT ERROR] Response body: {e.body[:500]}", flush=True)
|
||||||
|
raw_openai = {"error": str(e)}
|
||||||
|
reddit_error = f"API error: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[REDDIT ERROR] Unexpected error: {type(e).__name__}: {e}", flush=True)
|
||||||
|
raw_openai = {"error": str(e)}
|
||||||
|
reddit_error = f"{type(e).__name__}: {e}"
|
||||||
|
|
||||||
# Parse response
|
# Parse response
|
||||||
reddit_items = openai_reddit.parse_reddit_response(raw_openai)
|
reddit_items = openai_reddit.parse_reddit_response(raw_openai)
|
||||||
@@ -112,7 +126,7 @@ def run_research(
|
|||||||
# Parse response
|
# Parse response
|
||||||
x_items = xai_x.parse_x_response(raw_xai)
|
x_items = xai_x.parse_x_response(raw_xai)
|
||||||
|
|
||||||
return reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched
|
return reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -227,7 +241,7 @@ def main():
|
|||||||
mode = "x-only"
|
mode = "x-only"
|
||||||
|
|
||||||
# Run research
|
# Run research
|
||||||
reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched = run_research(
|
reddit_items, x_items, raw_openai, raw_xai, raw_reddit_enriched, reddit_error, x_error = run_research(
|
||||||
args.topic,
|
args.topic,
|
||||||
sources,
|
sources,
|
||||||
config,
|
config,
|
||||||
@@ -265,6 +279,8 @@ def main():
|
|||||||
)
|
)
|
||||||
report.reddit = deduped_reddit
|
report.reddit = deduped_reddit
|
||||||
report.x = deduped_x
|
report.x = deduped_x
|
||||||
|
report.reddit_error = reddit_error
|
||||||
|
report.x_error = x_error
|
||||||
|
|
||||||
# Generate context snippet
|
# Generate context snippet
|
||||||
report.context_snippet_md = render.render_context_snippet(report)
|
report.context_snippet_md = render.render_context_snippet(report)
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ def parse_reddit_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|||||||
"""
|
"""
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
|
# Check for API errors first
|
||||||
|
if "error" in response and response["error"]:
|
||||||
|
error = response["error"]
|
||||||
|
err_msg = error.get("message", str(error)) if isinstance(error, dict) else str(error)
|
||||||
|
print(f"[REDDIT ERROR] OpenAI API error: {err_msg}", flush=True)
|
||||||
|
return items
|
||||||
|
|
||||||
# Try to find the output text
|
# Try to find the output text
|
||||||
output_text = ""
|
output_text = ""
|
||||||
if "output" in response:
|
if "output" in response:
|
||||||
@@ -131,6 +138,7 @@ def parse_reddit_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not output_text:
|
if not output_text:
|
||||||
|
print(f"[REDDIT WARNING] No output text found in OpenAI response. Keys present: {list(response.keys())}", flush=True)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
# Extract JSON from the response
|
# Extract JSON from the response
|
||||||
|
|||||||
+22
-2
@@ -46,7 +46,17 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Reddit items
|
# Reddit items
|
||||||
if report.reddit:
|
if report.reddit_error:
|
||||||
|
lines.append("### Reddit Threads")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"**ERROR:** {report.reddit_error}")
|
||||||
|
lines.append("")
|
||||||
|
elif report.mode in ("both", "reddit-only") and not report.reddit:
|
||||||
|
lines.append("### Reddit Threads")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("*No relevant Reddit threads found for this topic.*")
|
||||||
|
lines.append("")
|
||||||
|
elif report.reddit:
|
||||||
lines.append("### Reddit Threads")
|
lines.append("### Reddit Threads")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
for item in report.reddit[:limit]:
|
for item in report.reddit[:limit]:
|
||||||
@@ -78,7 +88,17 @@ def render_compact(report: schema.Report, limit: int = 15) -> str:
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# X items
|
# X items
|
||||||
if report.x:
|
if report.x_error:
|
||||||
|
lines.append("### X Posts")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"**ERROR:** {report.x_error}")
|
||||||
|
lines.append("")
|
||||||
|
elif report.mode in ("both", "x-only") and not report.x:
|
||||||
|
lines.append("### X Posts")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("*No relevant X posts found for this topic.*")
|
||||||
|
lines.append("")
|
||||||
|
elif report.x:
|
||||||
lines.append("### X Posts")
|
lines.append("### X Posts")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
for item in report.x[:limit]:
|
for item in report.x[:limit]:
|
||||||
|
|||||||
@@ -153,9 +153,12 @@ class Report:
|
|||||||
best_practices: List[str] = field(default_factory=list)
|
best_practices: List[str] = field(default_factory=list)
|
||||||
prompt_pack: List[str] = field(default_factory=list)
|
prompt_pack: List[str] = field(default_factory=list)
|
||||||
context_snippet_md: str = ""
|
context_snippet_md: str = ""
|
||||||
|
# Status tracking
|
||||||
|
reddit_error: Optional[str] = None
|
||||||
|
x_error: Optional[str] = None
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {
|
d = {
|
||||||
'topic': self.topic,
|
'topic': self.topic,
|
||||||
'range': {
|
'range': {
|
||||||
'from': self.range_from,
|
'from': self.range_from,
|
||||||
@@ -171,6 +174,11 @@ class Report:
|
|||||||
'prompt_pack': self.prompt_pack,
|
'prompt_pack': self.prompt_pack,
|
||||||
'context_snippet_md': self.context_snippet_md,
|
'context_snippet_md': self.context_snippet_md,
|
||||||
}
|
}
|
||||||
|
if self.reddit_error:
|
||||||
|
d['reddit_error'] = self.reddit_error
|
||||||
|
if self.x_error:
|
||||||
|
d['x_error'] = self.x_error
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def create_report(
|
def create_report(
|
||||||
|
|||||||
Reference in New Issue
Block a user