SPARKNET / src /agents /scenario1 /outreach_agent.py
MHamdan's picture
Initial commit: SPARKNET framework
a9dc537
"""
OutreachAgent for Patent Wake-Up Scenario
Generates valorization materials and outreach communications:
- Comprehensive valorization briefs (PDF)
- Executive summaries
- Stakeholder-specific outreach materials
"""
from typing import List
import os
from datetime import datetime
from loguru import logger
from langchain_core.prompts import ChatPromptTemplate
from ..base_agent import BaseAgent, Task
from ...llm.langchain_ollama_client import LangChainOllamaClient
from ...workflow.langgraph_state import (
PatentAnalysis,
MarketAnalysis,
StakeholderMatch,
ValorizationBrief
)
class OutreachAgent(BaseAgent):
"""
Specialized agent for generating valorization materials.
Creates briefs, summaries, and outreach communications.
"""
def __init__(self, llm_client: LangChainOllamaClient, memory_agent=None):
"""
Initialize OutreachAgent.
Args:
llm_client: LangChain Ollama client
memory_agent: Optional memory agent
"""
# Note: OutreachAgent uses LangChain directly
self.name = "OutreachAgent"
self.description = "Valorization brief and outreach generation"
self.llm_client = llm_client
self.memory_agent = memory_agent
# Use standard model for document generation
self.llm = llm_client.get_llm('standard') # llama3.1:8b
# Create generation chains
self.brief_chain = self._create_brief_chain()
self.summary_chain = self._create_summary_chain()
# Ensure outputs directory exists
os.makedirs("outputs", exist_ok=True)
logger.info("Initialized OutreachAgent")
def _create_brief_chain(self):
"""Create chain for valorization brief generation"""
prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert in technology commercialization and professional business writing."),
("human", """
Create a comprehensive valorization brief for this patent.
PATENT ANALYSIS:
Title: {patent_title}
TRL: {trl_level}/9
Key Innovations:
{key_innovations}
Potential Applications:
{applications}
MARKET OPPORTUNITIES:
{market_opportunities}
TOP STAKEHOLDER MATCHES:
{stakeholder_matches}
Create a professional valorization brief in markdown format with:
# Valorization Brief: [Patent Title]
## Executive Summary
[1-paragraph overview highlighting commercialization potential]
## Technology Overview
### Key Innovations
[Bullet points of key innovations]
### Technology Readiness
[TRL assessment and readiness for commercialization]
### Technical Advantages
[What makes this technology unique]
## Market Opportunity Analysis
### Target Sectors
[Top 3-5 sectors with market size data]
### Market Gaps Addressed
[Specific problems this solves]
### Competitive Positioning
[How to position vs. alternatives]
## Recommended Partners
[Top 5 stakeholders with match rationale]
## Commercialization Roadmap
### Immediate Next Steps (0-6 months)
[Specific actions]
### Medium-term Goals (6-18 months)
[Development milestones]
### Long-term Vision (18+ months)
[Market expansion]
## Key Takeaways
[3-5 bullet points with main insights]
Write professionally but accessibly. Use specific numbers and data where available.
""")
])
return prompt | self.llm
def _create_summary_chain(self):
"""Create chain for executive summary extraction"""
prompt = ChatPromptTemplate.from_messages([
("system", "You extract concise executive summaries from longer documents."),
("human", "Extract a 2-3 sentence executive summary from this brief:\n\n{brief_content}")
])
return prompt | self.llm
async def create_valorization_brief(
self,
patent_analysis: PatentAnalysis,
market_analysis: MarketAnalysis,
matches: List[StakeholderMatch]
) -> ValorizationBrief:
"""
Generate comprehensive valorization brief.
Args:
patent_analysis: Patent technical analysis
market_analysis: Market opportunities
matches: Stakeholder matches
Returns:
ValorizationBrief with content and PDF path
"""
logger.info(f"📝 Creating valorization brief for: {patent_analysis.title}")
# Format data for brief generation
key_innovations = "\n".join([f"- {inn}" for inn in patent_analysis.key_innovations])
applications = "\n".join([f"- {app}" for app in patent_analysis.potential_applications])
market_opps = "\n\n".join([
f"**{opp.sector}** ({opp.technology_fit} fit)\n"
f"- Market Size: {f'${opp.market_size_usd/1e9:.1f}B USD' if opp.market_size_usd is not None else 'NaN'}\n"
f"- Growth: {f'{opp.growth_rate_percent}% annually' if opp.growth_rate_percent is not None else 'NaN'}\n"
f"- Gap: {opp.market_gap}"
for opp in market_analysis.opportunities[:5]
])
stakeholder_text = "\n\n".join([
f"{i+1}. **{m.stakeholder_name}** ({m.stakeholder_type})\n"
f" - Location: {m.location}\n"
f" - Fit Score: {m.overall_fit_score:.2f}\n"
f" - Why: {m.match_rationale[:200]}..."
for i, m in enumerate(matches[:5])
])
# Generate brief content
logger.info("Generating brief content...")
content_response = await self.brief_chain.ainvoke({
"patent_title": patent_analysis.title,
"trl_level": patent_analysis.trl_level,
"key_innovations": key_innovations,
"applications": applications,
"market_opportunities": market_opps,
"stakeholder_matches": stakeholder_text
})
content = content_response.content
# Extract executive summary
logger.info("Extracting executive summary...")
summary_response = await self.summary_chain.ainvoke({
"brief_content": content[:2000] # First part only
})
executive_summary = summary_response.content
# Generate PDF
pdf_path = await self._generate_pdf(
content=content,
patent_id=patent_analysis.patent_id,
title=patent_analysis.title
)
# Build ValorizationBrief
brief = ValorizationBrief(
patent_id=patent_analysis.patent_id,
content=content,
pdf_path=pdf_path,
executive_summary=executive_summary,
technology_overview=self._extract_section(content, "Technology Overview"),
market_analysis_summary=self._extract_section(content, "Market Opportunity"),
partner_recommendations=self._extract_section(content, "Recommended Partners"),
top_opportunities=market_analysis.top_sectors,
recommended_partners=[m.stakeholder_name for m in matches[:5]],
key_takeaways=self._extract_takeaways(content),
generated_date=datetime.now().strftime("%Y-%m-%d"),
version="1.0"
)
logger.success(f"✅ Valorization brief created: {pdf_path}")
return brief
async def _generate_pdf(self, content: str, patent_id: str, title: str) -> str:
"""
Generate PDF from markdown content.
Args:
content: Markdown content
patent_id: Patent identifier
title: Brief title
Returns:
Path to generated PDF
"""
try:
from ...tools.langchain_tools import document_generator_tool
# Create filename
filename = f"valorization_brief_{patent_id}_{datetime.now().strftime('%Y%m%d')}.pdf"
pdf_path = os.path.join("outputs", filename)
# Generate PDF
await document_generator_tool.ainvoke({
"output_path": pdf_path,
"title": f"Valorization Brief: {title}",
"content": content,
"author": "SPARKNET Valorization System"
})
return pdf_path
except Exception as e:
logger.error(f"PDF generation failed: {e}")
# Fallback: save as markdown
md_path = pdf_path.replace('.pdf', '.md')
with open(md_path, 'w', encoding='utf-8') as f:
f.write(content)
logger.warning(f"Saved as markdown instead: {md_path}")
return md_path
def _extract_section(self, content: str, section_name: str) -> str:
"""Extract a specific section from markdown content"""
import re
# Find section using markdown headers
pattern = rf'##\s+{section_name}.*?\n(.*?)(?=##|\Z)'
match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)
if match:
return match.group(1).strip()[:500] # Limit length
return "Section not found"
def _extract_takeaways(self, content: str) -> List[str]:
"""Extract key takeaways from content"""
import re
# Look for Key Takeaways section
pattern = r'##\s+Key Takeaways.*?\n(.*?)(?=##|\Z)'
match = re.search(pattern, content, re.DOTALL | re.IGNORECASE)
if match:
takeaways_text = match.group(1)
# Extract bullet points
bullets = re.findall(r'[-*]\s+(.+)', takeaways_text)
return bullets[:5]
# Fallback: create generic takeaways
return [
"Technology demonstrates strong commercialization potential",
"Multiple market opportunities identified",
"Strategic partners available for collaboration"
]
async def process_task(self, task: Task) -> Task:
"""
Process task using agent interface.
Args:
task: Task with patent_analysis, market_analysis, and matches in metadata
Returns:
Task with ValorizationBrief result
"""
task.status = "in_progress"
try:
patent_dict = task.metadata.get('patent_analysis')
market_dict = task.metadata.get('market_analysis')
matches_list = task.metadata.get('matches', [])
if not patent_dict or not market_dict:
raise ValueError("patent_analysis and market_analysis required")
# Convert dicts to objects
patent_analysis = PatentAnalysis(**patent_dict)
market_analysis = MarketAnalysis(**market_dict)
matches = [StakeholderMatch(**m) for m in matches_list]
brief = await self.create_valorization_brief(
patent_analysis,
market_analysis,
matches
)
task.result = brief.model_dump()
task.status = "completed"
except Exception as e:
logger.error(f"Outreach generation failed: {e}")
task.status = "failed"
task.error = str(e)
return task