""" 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