-
Notifications
You must be signed in to change notification settings - Fork 15.9k
feat: structured output for Social Media Analyst (#796) #802
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 | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,7 +19,7 @@ | |||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from enum import Enum | ||||||||||||||||||||||||
| from typing import Optional | ||||||||||||||||||||||||
| from typing import Literal, Optional | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| from pydantic import BaseModel, Field | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -226,3 +226,78 @@ def render_pm_decision(decision: PortfolioDecision) -> str: | |||||||||||||||||||||||
| if decision.time_horizon: | ||||||||||||||||||||||||
| parts.extend(["", f"**Time Horizon**: {decision.time_horizon}"]) | ||||||||||||||||||||||||
| return "\n".join(parts) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||
| # Social Media / Sentiment Analyst | ||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class SentimentBand(str, Enum): | ||||||||||||||||||||||||
| """Categorical sentiment label used by the Social Media Analyst.""" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| BULLISH = "Bullish" | ||||||||||||||||||||||||
| MILDLY_BULLISH = "Mildly Bullish" | ||||||||||||||||||||||||
| NEUTRAL = "Neutral" | ||||||||||||||||||||||||
| MIXED = "Mixed" | ||||||||||||||||||||||||
| MILDLY_BEARISH = "Mildly Bearish" | ||||||||||||||||||||||||
| BEARISH = "Bearish" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| class SentimentReport(BaseModel): | ||||||||||||||||||||||||
| """Structured sentiment output produced by the Social Media Analyst. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| The analyst collects data via tool calls (get_news), writes a free-form | ||||||||||||||||||||||||
| research narrative, then this schema is used to extract deterministic | ||||||||||||||||||||||||
| summary fields from that narrative. The narrative field preserves the | ||||||||||||||||||||||||
| full prose so downstream debate agents receive the same rich context | ||||||||||||||||||||||||
| they always have. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| overall_score: float = Field( | ||||||||||||||||||||||||
| ge=0, | ||||||||||||||||||||||||
| le=10, | ||||||||||||||||||||||||
| description=( | ||||||||||||||||||||||||
| "Numeric sentiment score from 0 (most bearish) to 10 (most bullish). " | ||||||||||||||||||||||||
| "Use one decimal place, e.g. 7.2." | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| overall_band: SentimentBand = Field( | ||||||||||||||||||||||||
| description=( | ||||||||||||||||||||||||
| "Categorical sentiment label. Pick the band that best matches the " | ||||||||||||||||||||||||
| "overall_score: Bullish (7.1-10), Mildly Bullish (5.6-7.0), Neutral " | ||||||||||||||||||||||||
| "(4.5-5.5, flat outlook with no strong signal), Mixed " | ||||||||||||||||||||||||
| "(4.5-5.5, high-volume but conflicting signals), " | ||||||||||||||||||||||||
| "Mildly Bearish (3.0-4.4), Bearish (0-2.9)." | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| confidence: Literal["low", "medium", "high"] = Field( | ||||||||||||||||||||||||
| description=( | ||||||||||||||||||||||||
| "Confidence in the sentiment assessment based on data availability and " | ||||||||||||||||||||||||
| "signal clarity. Use 'low' when sources are sparse or contradictory, " | ||||||||||||||||||||||||
| "'medium' for moderate signal, 'high' for strong consistent signal." | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| narrative: str = Field( | ||||||||||||||||||||||||
| description=( | ||||||||||||||||||||||||
| "The full analyst report in markdown. Include all research findings, " | ||||||||||||||||||||||||
| "social media analysis, news insights, and a summary table. " | ||||||||||||||||||||||||
| "This is what downstream agents read as context." | ||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| def render_sentiment_report(report: SentimentReport) -> str: | ||||||||||||||||||||||||
| """Render a SentimentReport to markdown for state storage and downstream agents. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| The narrative (full prose report) is preserved intact so bull/bear researchers | ||||||||||||||||||||||||
| and risk debate agents continue to receive the same rich context. The structured | ||||||||||||||||||||||||
| fields are appended as a summary header for quick downstream parsing. | ||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||
| header = "\n".join([ | ||||||||||||||||||||||||
| f"**Overall Sentiment:** **{report.overall_band.value}** " | ||||||||||||||||||||||||
| f"(Score: {report.overall_score:.1f}/10, Confidence: {report.confidence})", | ||||||||||||||||||||||||
| "", | ||||||||||||||||||||||||
| "", | ||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||
|
Comment on lines
+297
to
+302
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. The current implementation of
Suggested change
Author
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. Fixed in 1ec8997 - added the extra blank line for proper markdown block separation. |
||||||||||||||||||||||||
| return header + report.narrative | ||||||||||||||||||||||||
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.
The description for
overall_bandcontains overlapping numeric ranges (e.g., 7.0 is in both Bullish and Mildly Bullish) and groupsNeutral/Mixedtogether even though they are separate members in theSentimentBandenum. Providing non-overlapping ranges and a clear distinction between 'Neutral' and 'Mixed' will help the LLM select the correct enum value more reliably.'Neutral' typically implies a lack of strong signal or a flat outlook, while 'Mixed' implies high-volume but conflicting signals.
Uh oh!
There was an error while loading. Please reload this page.
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.
Fixed in 1ec8997 - non-overlapping ranges applied and Neutral vs Mixed distinction clarified.