You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
3.8 KiB
116 lines
3.8 KiB
from __future__ import annotations
|
|
import asyncio
|
|
import logging
|
|
|
|
from duckduckgo_search import DDGS
|
|
|
|
from app.models.agent import AgentPersona
|
|
from app.models.world import WorldState
|
|
from app.services.llm import LLMClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
RESEARCH_SUMMARIZE_SYSTEM = """You are {name}, a {age}-year-old {role}.
|
|
Personality: honesty={honesty:.1f}, ambition={ambition:.1f}, empathy={empathy:.1f}, conformity={conformity:.1f}
|
|
{market_personality}
|
|
|
|
Your current beliefs:
|
|
{beliefs}
|
|
|
|
You just searched the internet for: "{query}"
|
|
Reason: {reason}
|
|
|
|
Here are the search results:
|
|
{search_results}
|
|
|
|
Based on YOUR personality and beliefs, what do you take away from this?
|
|
Write 2-3 sentences of what you learned and how it affects your thinking.
|
|
Be specific — cite numbers, names, or facts you found.
|
|
If the results contradict your beliefs, note the tension.
|
|
If they confirm your beliefs, note the reinforcement."""
|
|
|
|
|
|
class ResearchService:
|
|
def __init__(self, llm: LLMClient, enabled: bool = True, max_per_round: int = 5):
|
|
self.llm = llm
|
|
self.enabled = enabled
|
|
self.max_per_round = max_per_round
|
|
|
|
async def _raw_search(self, query: str, max_results: int = 5) -> list[dict]:
|
|
"""Run a DuckDuckGo search in a thread pool to avoid blocking the event loop."""
|
|
def _search():
|
|
try:
|
|
with DDGS() as ddgs:
|
|
return list(ddgs.text(query, max_results=max_results))
|
|
except Exception as e:
|
|
logger.warning("DuckDuckGo search failed for '%s': %s", query, e)
|
|
return []
|
|
|
|
loop = asyncio.get_running_loop()
|
|
return await loop.run_in_executor(None, _search)
|
|
|
|
async def search_and_summarize(
|
|
self,
|
|
query: str,
|
|
reason: str,
|
|
agent: AgentPersona,
|
|
world_state: WorldState,
|
|
) -> str:
|
|
if not self.enabled:
|
|
return ""
|
|
|
|
results = await self._raw_search(query)
|
|
if not results:
|
|
return ""
|
|
|
|
snippets = []
|
|
for i, r in enumerate(results[:5], 1):
|
|
title = r.get("title", "")
|
|
body = r.get("body", "")
|
|
href = r.get("href", "")
|
|
snippets.append(f"{i}. {title}\n {body}\n Source: {href}")
|
|
|
|
search_results_text = "\n\n".join(snippets)
|
|
|
|
p = agent.personality
|
|
market_personality = ""
|
|
has_market_traits = (
|
|
p.brand_loyalty != 0.5 or p.price_sensitivity != 0.5
|
|
or p.social_proof != 0.5 or p.novelty_seeking != 0.5
|
|
)
|
|
if has_market_traits:
|
|
market_personality = (
|
|
f"Consumer traits: brand_loyalty={p.brand_loyalty:.1f}, "
|
|
f"price_sensitivity={p.price_sensitivity:.1f}, "
|
|
f"social_proof={p.social_proof:.1f}, "
|
|
f"novelty_seeking={p.novelty_seeking:.1f}"
|
|
)
|
|
|
|
beliefs = "\n".join(f"- {b}" for b in agent.beliefs[:5]) or "Still forming opinions."
|
|
|
|
system = RESEARCH_SUMMARIZE_SYSTEM.format(
|
|
name=agent.name,
|
|
age=agent.age,
|
|
role=agent.role,
|
|
honesty=p.honesty,
|
|
ambition=p.ambition,
|
|
empathy=p.empathy,
|
|
conformity=p.conformity,
|
|
market_personality=market_personality,
|
|
beliefs=beliefs,
|
|
query=query,
|
|
reason=reason,
|
|
search_results=search_results_text,
|
|
)
|
|
|
|
try:
|
|
digest = await self.llm.generate(
|
|
system=system,
|
|
user="What did you learn? Summarize in 2-3 sentences.",
|
|
json_mode=False,
|
|
max_tokens=200,
|
|
)
|
|
return digest.strip()
|
|
except Exception as e:
|
|
logger.warning("Research summarization failed for agent %s: %s", agent.name, e)
|
|
return ""
|
|
|