fix(cli): resolve UnicodeEncodeError on Windows during report generation
- Added explicit `encoding="utf-8"` to all `open()` and `pathlib.Path.write_text()` calls in `cli/main.py`. - Fixes application crashes on Windows when the LLM outputs non-ASCII characters (like emojis) during log writing and markdown report generation.
This commit is contained in:
parent
fa4d01c23a
commit
602e75b05f
34
cli/main.py
34
cli/main.py
|
|
@ -463,7 +463,7 @@ def update_display(layout, spinner_text=None, stats_handler=None, start_time=Non
|
|||
def get_user_selections():
|
||||
"""Get all user selections before starting the analysis display."""
|
||||
# Display ASCII art welcome message
|
||||
with open(Path(__file__).parent / "static" / "welcome.txt", "r") as f:
|
||||
with open(Path(__file__).parent / "static" / "welcome.txt", "r", encoding="utf-8") as f:
|
||||
welcome_ascii = f.read()
|
||||
|
||||
# Create welcome box content
|
||||
|
|
@ -646,19 +646,19 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
analyst_parts = []
|
||||
if final_state.get("market_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "market.md").write_text(final_state["market_report"])
|
||||
(analysts_dir / "market.md").write_text(final_state["market_report"], encoding="utf-8")
|
||||
analyst_parts.append(("Market Analyst", final_state["market_report"]))
|
||||
if final_state.get("sentiment_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "sentiment.md").write_text(final_state["sentiment_report"])
|
||||
(analysts_dir / "sentiment.md").write_text(final_state["sentiment_report"], encoding="utf-8")
|
||||
analyst_parts.append(("Social Analyst", final_state["sentiment_report"]))
|
||||
if final_state.get("news_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "news.md").write_text(final_state["news_report"])
|
||||
(analysts_dir / "news.md").write_text(final_state["news_report"], encoding="utf-8")
|
||||
analyst_parts.append(("News Analyst", final_state["news_report"]))
|
||||
if final_state.get("fundamentals_report"):
|
||||
analysts_dir.mkdir(exist_ok=True)
|
||||
(analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"])
|
||||
(analysts_dir / "fundamentals.md").write_text(final_state["fundamentals_report"], encoding="utf-8")
|
||||
analyst_parts.append(("Fundamentals Analyst", final_state["fundamentals_report"]))
|
||||
if analyst_parts:
|
||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in analyst_parts)
|
||||
|
|
@ -671,15 +671,15 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
research_parts = []
|
||||
if debate.get("bull_history"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "bull.md").write_text(debate["bull_history"])
|
||||
(research_dir / "bull.md").write_text(debate["bull_history"], encoding="utf-8")
|
||||
research_parts.append(("Bull Researcher", debate["bull_history"]))
|
||||
if debate.get("bear_history"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "bear.md").write_text(debate["bear_history"])
|
||||
(research_dir / "bear.md").write_text(debate["bear_history"], encoding="utf-8")
|
||||
research_parts.append(("Bear Researcher", debate["bear_history"]))
|
||||
if debate.get("judge_decision"):
|
||||
research_dir.mkdir(exist_ok=True)
|
||||
(research_dir / "manager.md").write_text(debate["judge_decision"])
|
||||
(research_dir / "manager.md").write_text(debate["judge_decision"], encoding="utf-8")
|
||||
research_parts.append(("Research Manager", debate["judge_decision"]))
|
||||
if research_parts:
|
||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in research_parts)
|
||||
|
|
@ -689,7 +689,7 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
if final_state.get("trader_investment_plan"):
|
||||
trading_dir = save_path / "3_trading"
|
||||
trading_dir.mkdir(exist_ok=True)
|
||||
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"])
|
||||
(trading_dir / "trader.md").write_text(final_state["trader_investment_plan"], encoding="utf-8")
|
||||
sections.append(f"## III. Trading Team Plan\n\n### Trader\n{final_state['trader_investment_plan']}")
|
||||
|
||||
# 4. Risk Management
|
||||
|
|
@ -699,15 +699,15 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
risk_parts = []
|
||||
if risk.get("aggressive_history"):
|
||||
risk_dir.mkdir(exist_ok=True)
|
||||
(risk_dir / "aggressive.md").write_text(risk["aggressive_history"])
|
||||
(risk_dir / "aggressive.md").write_text(risk["aggressive_history"], encoding="utf-8")
|
||||
risk_parts.append(("Aggressive Analyst", risk["aggressive_history"]))
|
||||
if risk.get("conservative_history"):
|
||||
risk_dir.mkdir(exist_ok=True)
|
||||
(risk_dir / "conservative.md").write_text(risk["conservative_history"])
|
||||
(risk_dir / "conservative.md").write_text(risk["conservative_history"], encoding="utf-8")
|
||||
risk_parts.append(("Conservative Analyst", risk["conservative_history"]))
|
||||
if risk.get("neutral_history"):
|
||||
risk_dir.mkdir(exist_ok=True)
|
||||
(risk_dir / "neutral.md").write_text(risk["neutral_history"])
|
||||
(risk_dir / "neutral.md").write_text(risk["neutral_history"], encoding="utf-8")
|
||||
risk_parts.append(("Neutral Analyst", risk["neutral_history"]))
|
||||
if risk_parts:
|
||||
content = "\n\n".join(f"### {name}\n{text}" for name, text in risk_parts)
|
||||
|
|
@ -717,12 +717,12 @@ def save_report_to_disk(final_state, ticker: str, save_path: Path):
|
|||
if risk.get("judge_decision"):
|
||||
portfolio_dir = save_path / "5_portfolio"
|
||||
portfolio_dir.mkdir(exist_ok=True)
|
||||
(portfolio_dir / "decision.md").write_text(risk["judge_decision"])
|
||||
(portfolio_dir / "decision.md").write_text(risk["judge_decision"], encoding="utf-8")
|
||||
sections.append(f"## V. Portfolio Manager Decision\n\n### Portfolio Manager\n{risk['judge_decision']}")
|
||||
|
||||
# Write consolidated report
|
||||
header = f"# Trading Analysis Report: {ticker}\n\nGenerated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||||
(save_path / "complete_report.md").write_text(header + "\n\n".join(sections))
|
||||
(save_path / "complete_report.md").write_text(header + "\n\n".join(sections), encoding="utf-8")
|
||||
return save_path / "complete_report.md"
|
||||
|
||||
|
||||
|
|
@ -980,7 +980,7 @@ def run_analysis():
|
|||
func(*args, **kwargs)
|
||||
timestamp, message_type, content = obj.messages[-1]
|
||||
content = content.replace("\n", " ") # Replace newlines with spaces
|
||||
with open(log_file, "a") as f:
|
||||
with open(log_file, "a", encoding="utf-8") as f:
|
||||
f.write(f"{timestamp} [{message_type}] {content}\n")
|
||||
return wrapper
|
||||
|
||||
|
|
@ -991,7 +991,7 @@ def run_analysis():
|
|||
func(*args, **kwargs)
|
||||
timestamp, tool_name, args = obj.tool_calls[-1]
|
||||
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
|
||||
with open(log_file, "a") as f:
|
||||
with open(log_file, "a", encoding="utf-8") as f:
|
||||
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
|
||||
return wrapper
|
||||
|
||||
|
|
@ -1005,7 +1005,7 @@ def run_analysis():
|
|||
if content:
|
||||
file_name = f"{section_name}.md"
|
||||
text = "\n".join(str(item) for item in content) if isinstance(content, list) else content
|
||||
with open(report_dir / file_name, "w") as f:
|
||||
with open(report_dir / file_name, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
return wrapper
|
||||
|
||||
|
|
|
|||
|
|
@ -102,14 +102,15 @@ MODEL_OPTIONS: ProviderModeOptions = {
|
|||
# OpenRouter: fetched dynamically. Azure: any deployed model name.
|
||||
"ollama": {
|
||||
"quick": [
|
||||
("Gemma4:e4b (4B, local)", "gemma4:e4b"),
|
||||
("Qwen3:latest (8B, local)", "qwen3:latest"),
|
||||
("GPT-OSS:latest (20B, local)", "gpt-oss:latest"),
|
||||
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
||||
],
|
||||
"deep": [
|
||||
("Gemma4:e4b (4B, local)", "gemma4:e4b"),
|
||||
("GLM-4.7-Flash:latest (30B, local)", "glm-4.7-flash:latest"),
|
||||
("GPT-OSS:latest (20B, local)", "gpt-oss:latest"),
|
||||
("Qwen3:latest (8B, local)", "qwen3:latest"),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue