·29 min

Complete Guide: Building an AI Knowledge Companion with Browser-Use, MCP, and Ollama for Advanced Web Automation and Information Processing

A comprehensive guide to building an AI-powered knowledge companion system that combines Browser-Use for web automation, Model Context Protocol (MCP) for tool integration, and Ollama for local LLM processing to create an intelligent research and information gathering assistant.

DK

Daniel Kliewer

Author, Sovereign AI

Browser-UseMCPOllamaAI Knowledge CompanionWeb AutomationInformation ProcessingLocal LLMsAI AgentsSemantic SearchIntelligent Research
Sovereign AI book cover

From the Book

This is from Sovereign AI: Building Local-First Intelligent Systems.

Get the Book — $88
Complete Guide: Building an AI Knowledge Companion with Browser-Use, MCP, and Ollama for Advanced Web Automation and Information Processing

Image

Beyond Research: Building a Modern AI Knowledge Companion

A Comprehensive Guide to Browser-Use, MCP, and AI-Powered Information Processing

1. Introduction to AI-Powered Knowledge Systems

In today's information landscape, the ability to efficiently gather, process, and synthesize knowledge has become essential. This guide transforms the concept of a basic research assistant into a comprehensive AI Knowledge Companion system—a versatile tool that not only conducts research but acts as your digital extension in navigating the vast information ecosystem.

What is Browser-Use? Browser-Use is a programmable interface that enables AI systems to interact with web browsers just as humans do—visiting websites, clicking links, filling forms, and extracting information. Unlike simple web scraping, Browser-Use provides true browser automation that can handle modern, JavaScript-heavy websites, captchas, and complex user interactions.

What is MCP (Model Context Protocol)? The Model Context Protocol is a standardized framework that facilitates secure communication between AI models and external tools or data sources. MCP defines how information is exchanged, permissions are granted, and results are returned, creating a universal "language" for AI systems to safely and effectively interface with the digital world.


2. Understanding the Core Technologies

Browser-Use: AI's Window to the Web

Browser-Use fundamentally transforms how AI interacts with the internet by:

  1. Providing visual context: Unlike API-based approaches, Browser-Use allows the AI to "see" what a human would see
  2. Enabling stateful navigation: Maintaining session information across multiple pages
  3. Handling dynamic content: Processing JavaScript-rendered pages that traditional scrapers cannot access
  4. Supporting authentication: Logging into services when needed

Implementation principle: Browser-Use creates a controlled browser instance that executes commands from your AI system through a dedicated interface, while feeding back visual and structural information about the pages it visits.

MCP: The Universal AI Connector

MCP serves as a standardized protocol for AI-to-tool communication, addressing several key challenges:

  1. Security: Defining clear permission boundaries and data access controls
  2. Interoperability: Creating a common language for diverse tools to connect to AI systems
  3. Context management: Efficiently transferring relevant information between systems
  4. Versioning and compatibility: Ensuring tools and AI models can evolve independently

Key concept: MCP treats external tools as "contexts" that an AI model can access, defining both how the AI can request information and how the external systems should respond.


3. Project Architecture: Building Your Knowledge Companion

System Overview

Our Knowledge Companion consists of five core components:

  1. User Interface: Accepts queries and displays results
  2. Orchestration Engine: Coordinates all system components
  3. LLM Core: Processes language, plans actions, and generates reports
  4. Browser-Use Module: Handles web navigation and extraction
  5. MCP Integration Layer: Connects to external knowledge sources

Component Interaction Flow

  1. User submits a query through the interface
  2. The orchestration engine passes the query to the LLM core
  3. The LLM plans a research strategy and generates actions
  4. Actions are executed through Browser-Use or MCP connections
  5. Retrieved information returns to the LLM for synthesis
  6. The final report is presented to the user

Design philosophy: This modular architecture allows each component to evolve independently while maintaining clear communication channels between them.


4. Setting Up Your Development Environment

Hardware and Software Requirements

For optimal performance, we recommend:

  • CPU: 4+ cores (8+ preferred)
  • RAM: 16GB minimum (32GB recommended)
  • Storage: 20GB free space (SSD preferred)
  • GPU: Optional but beneficial for larger models
  • Operating System: Linux, macOS, or Windows 10/11

Installation Process

  1. Python Environment Setup:
bash
1# Create a virtual environment
2python -m venv ai-companion
3source ai-companion/bin/activate # On Windows: ai-companion\Scripts\activate
4
5# Install core dependencies
6pip install browser-use ollama mcp-client pydantic fastapi uvicorn
  1. Ollama Configuration:
bash
1# Download Ollama from https://ollama.com
2# Then pull the Llama 3.2 model
3ollama pull llama3.2
4
5# Test the model
6ollama run llama3.2 "Hello, world!"
  1. Browser-Use Setup:
python
1# Test browser-use functionality
2from browser_use import BrowserSession
3
4browser = BrowserSession()
5browser.navigate("https://www.example.com")
6content = browser.get_page_content()
7print(content)
8browser.close()
  1. MCP Configuration:
python
1# Configure MCP client
2from mcp_client import MCPClient
3
4mcp = MCPClient(
5 server_url="https://your-mcp-server.com",
6 api_key="your_api_key",
7 default_timeout=30
8)
9
10# Test connection
11status = mcp.check_connection()
12print(f"MCP Connection: {status}")

Important concept: The separation between the LLM runtime (Ollama) and your application code creates a clean architecture that can adapt to different models and execution environments.


5. Implementing Browser-Use Intelligence

Understanding Browser Automation Principles

When implementing Browser-Use, it's essential to understand that we're creating an AI system that can:

  1. Form intentions: Decide what information to seek
  2. Execute navigation: Move through websites purposefully
  3. Extract information: Identify and collect relevant data
  4. Process results: Transform raw web content into structured knowledge

Creating a Robust Browser-Use Module

python
1class IntelligentBrowser:
2 def __init__(self, headless=True):
3 """Initialize browser session with configurable visibility."""
4 self.browser = BrowserSession(headless=headless)
5 self.history = []
6
7 def search(self, query, search_engine="google"):
8 """Perform a search using specified engine."""
9 if search_engine == "google":
10 self.browser.navigate("https://www.google.com")
11 search_box = self.browser.find_element('input[name="q"]')
12 self.browser.input_text(search_box, query)
13 self.browser.press_enter()
14 self.history.append({"action": "search", "query": query})
15 return self.get_search_results()
16
17 def get_search_results(self):
18 """Extract search results from the current page."""
19 results = []
20 elements = self.browser.find_elements("div.g")
21
22 for element in elements:
23 title_elem = self.browser.find_element_within(element, "h3")
24 link_elem = self.browser.find_element_within(element, "a")
25 snippet_elem = self.browser.find_element_within(element, "div.VwiC3b")
26
27 if title_elem and link_elem and snippet_elem:
28 title = self.browser.get_text(title_elem)
29 link = self.browser.get_attribute(link_elem, "href")
30 snippet = self.browser.get_text(snippet_elem)
31
32 results.append({
33 "title": title,
34 "url": link,
35 "snippet": snippet
36 })
37
38 return results
39
40 def visit_page(self, url):
41 """Navigate to a specific URL and extract content."""
42 self.browser.navigate(url)
43 self.history.append({"action": "visit", "url": url})
44
45 # Wait for page to load completely
46 self.browser.wait_for_page_load()
47
48 # Extract main content, avoiding navigation elements
49 content = self.extract_main_content()
50 return {
51 "url": url,
52 "title": self.browser.get_page_title(),
53 "content": content
54 }
55
56 def extract_main_content(self):
57 """Intelligently extract the main content from the current page."""
58 # Try common content selectors
59 content_selectors = [
60 "article", "main", ".content", "#content",
61 "[role='main']", ".post-content"
62 ]
63
64 for selector in content_selectors:
65 element = self.browser.find_element(selector)
66 if element:
67 return self.browser.get_text(element)
68
69 # Fallback: use heuristics to find the largest text block
70 paragraphs = self.browser.find_elements("p")
71 if paragraphs:
72 paragraph_texts = [self.browser.get_text(p) for p in paragraphs]
73 # Filter out very short paragraphs
74 substantial_paragraphs = [p for p in paragraph_texts if len(p) > 100]
75 if substantial_paragraphs:
76 return "\n\n".join(substantial_paragraphs)
77
78 # Last resort: get body text
79 return self.browser.get_body_text()
80
81 def close(self):
82 """Close the browser session."""
83 self.browser.close()

Key insight: The above implementation demonstrates how Browser-Use goes beyond simple scraping by making contextual decisions about what content is relevant, handling different site structures, and maintaining state across navigation.


6. Implementing the MCP Integration Layer

Understanding the Model Context Protocol

MCP enables standardized communication between your AI system and external tools through a structured protocol. Instead of custom code for each integration, MCP provides a unified framework for:

  1. Tool registration: Defining what tools are available
  2. Request formatting: Structuring how the AI requests information
  3. Response handling: Processing and validating tool outputs
  4. Error management: Handling failures in a consistent way

Building an MCP Client

python
1class KnowledgeSourceManager:
2 def __init__(self, mcp_client):
3 """Initialize with an MCP client."""
4 self.mcp = mcp_client
5 self.available_sources = self._discover_sources()
6
7 def _discover_sources(self):
8 """Query the MCP server for available knowledge sources."""
9 try:
10 sources = self.mcp.list_contexts()
11 return {
12 source["name"]: {
13 "description": source["description"],
14 "capabilities": source["capabilities"],
15 "parameters": source["parameters"]
16 } for source in sources
17 }
18 except Exception as e:
19 print(f"Error discovering sources: {e}")
20 return {}
21
22 def query_source(self, source_name, query_params):
23 """Query a specific knowledge source through MCP."""
24 if source_name not in self.available_sources:
25 raise ValueError(f"Unknown source: {source_name}")
26
27 try:
28 response = self.mcp.query_context(
29 context_name=source_name,
30 parameters=query_params
31 )
32 return response
33 except Exception as e:
34 print(f"Error querying {source_name}: {e}")
35 return {"error": str(e)}
36
37 def search_arxiv(self, query, max_results=5, categories=None):
38 """Specialized method for arXiv searches."""
39 params = {
40 "query": query,
41 "max_results": max_results
42 }
43
44 if categories:
45 params["categories"] = categories
46
47 return self.query_source("arxiv", params)
48
49 def search_wikipedia(self, query, depth=1):
50 """Specialized method for Wikipedia searches."""
51 params = {
52 "query": query,
53 "depth": depth # How many links to follow
54 }
55
56 return self.query_source("wikipedia", params)
57
58 def get_source_capabilities(self, source_name):
59 """Get detailed information about a knowledge source."""
60 if source_name in self.available_sources:
61 return self.available_sources[source_name]
62 return None

MCP concept in practice: This implementation shows how MCP creates a uniform interface to diverse knowledge sources. The AI doesn't need to know the specifics of the arXiv API or Wikipedia's structure—it just makes standardized requests through the MCP protocol.


7. The Orchestration Engine: Coordinating Your AI System

Understanding Orchestration

The orchestration engine is the "brain" of your Knowledge Companion, responsible for:

  1. Query analysis: Understanding what the user is asking
  2. Planning: Determining which tools to use and in what sequence
  3. Execution: Calling the appropriate components
  4. Integration: Combining information from multiple sources
  5. Presentation: Formatting the final output for the user

Implementing the Orchestrator

python
1class KnowledgeOrchestrator:
2 def __init__(self, llm_client, browser, knowledge_manager):
3 """Initialize with core components."""
4 self.llm = llm_client
5 self.browser = browser
6 self.knowledge_manager = knowledge_manager
7
8 async def process_query(self, user_query):
9 """Process a user query from start to finish."""
10 # Step 1: Analyze the query to determine approach
11 analysis = await self._analyze_query(user_query)
12
13 # Step 2: Execute the research plan
14 research_results = await self._execute_research_plan(analysis, user_query)
15
16 # Step 3: Synthesize the findings into a coherent response
17 final_report = await self._synthesize_report(user_query, research_results)
18
19 return final_report
20
21 async def _analyze_query(self, query):
22 """Use the LLM to analyze the query and create a research plan."""
23 prompt = f"""
24 Analyze the following research query and determine the best approach:
25
26 QUERY: {query}
27
28 Please determine:
29 1. What type of information is being requested?
30 2. Which knowledge sources would be most relevant (web search, arXiv, Wikipedia, etc.)?
31 3. What specific search terms should be used for each source?
32 4. What is the priority order for consulting these sources?
33 5. Are there any specialized domains or technical knowledge required?
34
35 Return your analysis as a structured JSON object.
36 """
37
38 response = await self.llm.complete(prompt)
39 return json.loads(response)
40
41 async def _execute_research_plan(self, analysis, original_query):
42 """Execute the research plan across multiple sources."""
43 results = []
44
45 # Execute based on the priority order determined in the analysis
46 for source in analysis["priority_order"]:
47 if source == "web_search":
48 search_terms = analysis["search_terms"]["web_search"]
49 web_results = await self._perform_web_research(search_terms)
50 results.append({
51 "source": "web_search",
52 "data": web_results
53 })
54
55 elif source == "arxiv":
56 if "arxiv" in analysis["search_terms"]:
57 arxiv_query = analysis["search_terms"]["arxiv"]
58 categories = analysis.get("arxiv_categories", None)
59 arxiv_results = self.knowledge_manager.search_arxiv(
60 arxiv_query,
61 categories=categories
62 )
63 results.append({
64 "source": "arxiv",
65 "data": arxiv_results
66 })
67
68 elif source == "wikipedia":
69 if "wikipedia" in analysis["search_terms"]:
70 wiki_query = analysis["search_terms"]["wikipedia"]
71 wiki_results = self.knowledge_manager.search_wikipedia(wiki_query)
72 results.append({
73 "source": "wikipedia",
74 "data": wiki_results
75 })
76
77 # If needed, perform follow-up research based on initial findings
78 if analysis.get("requires_followup", False):
79 followup_results = await self._perform_followup_research(results, original_query)
80 results.extend(followup_results)
81
82 return results
83
84 async def _perform_web_research(self, search_terms):
85 """Conduct web research using Browser-Use."""
86 web_results = []
87 for term in search_terms:
88 # Search and get results
89 search_results = self.browser.search(term)
90
91 # Visit the top 3 results and extract content
92 for result in search_results[:3]:
93 page_data = self.browser.visit_page(result["url"])
94 web_results.append({
95 "search_term": term,
96 "page_data": page_data
97 })
98
99 return web_results
100
101 async def _perform_followup_research(self, initial_results, original_query):
102 """Conduct follow-up research based on initial findings."""
103 # Generate follow-up questions using the LLM
104 prompt = f"""
105 Based on these initial research results and the original query:
106
107 ORIGINAL QUERY: {original_query}
108
109 INITIAL FINDINGS: {json.dumps(initial_results, indent=2)}
110
111 Generate 3 follow-up questions that would help complete the research.
112 Format as a JSON list of questions.
113 """
114
115 followup_response = await self.llm.complete(prompt)
116 followup_questions = json.loads(followup_response)
117
118 followup_results = []
119 for question in followup_questions:
120 # Recursively analyze and research each follow-up question
121 followup_analysis = await self._analyze_query(question)
122 question_results = await self._execute_research_plan(followup_analysis, question)
123 followup_results.append({
124 "followup_question": question,
125 "results": question_results
126 })
127
128 return followup_results
129
130 async def _synthesize_report(self, original_query, research_results):
131 """Synthesize research results into a comprehensive report."""
132 prompt = f"""
133 Create a comprehensive research report based on the following information:
134
135 ORIGINAL QUERY: {original_query}
136
137 RESEARCH FINDINGS: {json.dumps(research_results, indent=2)}
138
139 Your report should:
140 1. Start with an executive summary
141 2. Organize information logically by topic and source
142 3. Highlight key findings and insights
143 4. Note any contradictions or gaps in the research
144 5. Include relevant citations to original sources
145 6. End with conclusions and potential next steps
146
147 Format the report in Markdown for readability.
148 """
149
150 report = await self.llm.complete(prompt)
151 return report

Orchestration insight: The orchestrator demonstrates how to implement a multi-step research process that intelligently combines Browser-Use and MCP. Note how the system uses the LLM at multiple stages—for planning, for generating follow-up questions, and for synthesizing the final report.


8. Creating an Effective User Interface

Console-based Interface

For a simple but effective console interface:

python
1import asyncio
2import rich
3from rich.console import Console
4from rich.markdown import Markdown
5
6console = Console()
7
8class KnowledgeCompanionCLI:
9 def __init__(self, orchestrator):
10 self.orchestrator = orchestrator
11
12 async def start(self):
13 console.print("[bold blue]AI Knowledge Companion[/bold blue]")
14 console.print("Ask me anything, and I'll research it for you.")
15 console.print("Type 'exit' to quit.\n")
16
17 while True:
18 query = console.input("[bold green]Query:[/bold green] ")
19
20 if query.lower() in ('exit', 'quit'):
21 break
22
23 console.print("\n[italic]Researching your query...[/italic]")
24
25 try:
26 with console.status("[bold green]Thinking..."):
27 report = await self.orchestrator.process_query(query)
28
29 console.print("\n[bold]Research Results:[/bold]\n")
30 console.print(Markdown(report))
31
32 except Exception as e:
33 console.print(f"[bold red]Error:[/bold red] {str(e)}")
34
35 console.print("[bold blue]Thank you for using AI Knowledge Companion![/bold blue]")
36
37# Usage
38async def main():
39 # Setup components (simplified)
40 llm_client = LlamaClient()
41 browser = IntelligentBrowser()
42 mcp_client = MCPClient(server_url="https://mcp.example.com")
43 knowledge_manager = KnowledgeSourceManager(mcp_client)
44 orchestrator = KnowledgeOrchestrator(llm_client, browser, knowledge_manager)
45
46 # Start the CLI
47 cli = KnowledgeCompanionCLI(orchestrator)
48 await cli.start()
49
50 # Cleanup
51 browser.close()
52
53if __name__ == "__main__":
54 asyncio.run(main())

Web-based Interface (FastAPI)

For a more versatile web interface:

python
1from fastapi import FastAPI, BackgroundTasks
2from pydantic import BaseModel
3from typing import Optional, List
4import uvicorn
5
6app = FastAPI(title="AI Knowledge Companion API")
7
8# Data models
9class Query(BaseModel):
10 text: str
11 max_sources: Optional[int] = 5
12 preferred_sources: Optional[List[str]] = None
13
14class ResearchStatus(BaseModel):
15 query_id: str
16 status: str
17 progress: float
18 message: Optional[str] = None
19
20class ResearchReport(BaseModel):
21 query_id: str
22 query_text: str
23 report: str
24 sources: List[dict]
25 execution_time: float
26
27# In-memory storage for demo purposes
28research_tasks = {}
29
30@app.post("/research/start", response_model=ResearchStatus)
31async def start_research(query: Query, background_tasks: BackgroundTasks):
32 """Start a new research task."""
33 query_id = str(uuid.uuid4())
34
35 # Store initial status
36 research_tasks[query_id] = {
37 "status": "starting",
38 "progress": 0.0,
39 "query": query.text,
40 "report": None
41 }
42
43 # Launch research in background
44 background_tasks.add_task(
45 perform_research,
46 query_id,
47 query.text,
48 query.max_sources,
49 query.preferred_sources
50 )
51
52 return ResearchStatus(
53 query_id=query_id,
54 status="starting",
55 progress=0.0,
56 message="Research task initiated"
57 )
58
59@app.get("/research/{query_id}/status", response_model=ResearchStatus)
60async def get_research_status(query_id: str):
61 """Get the status of a research task."""
62 if query_id not in research_tasks:
63 raise HTTPException(status_code=404, detail="Research task not found")
64
65 task = research_tasks[query_id]
66 return ResearchStatus(
67 query_id=query_id,
68 status=task["status"],
69 progress=task["progress"],
70 message=task.get("message")
71 )
72
73@app.get("/research/{query_id}/report", response_model=ResearchReport)
74async def get_research_report(query_id: str):
75 """Get the final report of a completed research task."""
76 if query_id not in research_tasks:
77 raise HTTPException(status_code=404, detail="Research task not found")
78
79 task = research_tasks[query_id]
80
81 if task["status"] != "completed":
82 raise HTTPException(status_code=400, detail="Research not yet completed")
83
84 return ResearchReport(
85 query_id=query_id,
86 query_text=task["query"],
87 report=task["report"],
88 sources=task["sources"],
89 execution_time=task["execution_time"]
90 )
91
92async def perform_research(query_id, query_text, max_sources, preferred_sources):
93 """Background task to perform the actual research."""
94 try:
95 # Update status
96 research_tasks[query_id]["status"] = "researching"
97 research_tasks[query_id]["progress"] = 0.1
98
99 # Create components (simplified)
100 llm_client = LlamaClient()
101 browser = IntelligentBrowser()
102 mcp_client = MCPClient(server_url="https://mcp.example.com")
103 knowledge_manager = KnowledgeSourceManager(mcp_client)
104 orchestrator = KnowledgeOrchestrator(llm_client, browser, knowledge_manager)
105
106 # Update progress periodically
107 research_tasks[query_id]["progress"] = 0.3
108
109 # Perform the research
110 start_time = time.time()
111 report = await orchestrator.process_query(query_text)
112 end_time = time.time()
113
114 # Store the results
115 research_tasks[query_id].update({
116 "status": "completed",
117 "progress": 1.0,
118 "report": report,
119 "sources": orchestrator.get_used_sources(),
120 "execution_time": end_time - start_time
121 })
122
123 # Cleanup
124 browser.close()
125
126 except Exception as e:
127 research_tasks[query_id].update({
128 "status": "error",
129 "message": str(e)
130 })
131
132# Run with: uvicorn app:app --reload

UI design principle: Both interfaces demonstrate the importance of providing feedback during long-running research operations. The web interface adds asynchronous operation, allowing users to start research and check back later for results.


9. Advanced Features and Optimizations

Caching for Performance

Implement a caching layer to store frequently accessed information:

python
1import hashlib
2import json
3import aioredis
4from datetime import timedelta
5
6class KnowledgeCache:
7 def __init__(self, redis_url="redis://localhost"):
8 """Initialize the caching system."""
9 self.redis = None
10 self.redis_url = redis_url
11
12 async def connect(self):
13 """Connect to Redis."""
14 self.redis = await aioredis.create_redis_pool(self.redis_url)
15
16 async def close(self):
17 """Close Redis connection."""
18 if self.redis:
19 self.redis.close()
20 await self.redis.wait_closed()
21
22 async def get_cached_result(self, query, source):
23 """Try to get a cached result for a query from a specific source."""
24 if not self.redis:
25 return None
26
27 cache_key = self._make_cache_key(query, source)
28 cached_data = await self.redis.get(cache_key)
29
30 if cached_data:
31 return json.loads(cached_data)
32 return None
33
34 async def cache_result(self, query, source, result, ttl=timedelta(hours=24)):
35 """Cache a result with an expiration time."""
36 if not self.redis:
37 return
38
39 cache_key = self._make_cache_key(query, source)
40 await self.redis.set(
41 cache_key,
42 json.dumps(result),
43 expire=int(ttl.total_seconds())
44 )
45
46 def _make_cache_key(self, query, source):
47 """Create a deterministic cache key."""
48 data = f"{query}:{source}"
49 return f"knowledge_cache:{hashlib.md5(data.encode()).hexdigest()}"

Parallel Processing

Optimize research by executing multiple sources in parallel:

python
1async def _execute_research_plan(self, analysis, original_query):
2 """Execute the research plan with parallel processing."""
3 tasks = []
4
5 # Create tasks for each source in the research plan
6 for source in analysis["priority_order"]:
7 if source == "web_search" and "web_search" in analysis["search_terms"]:
8 search_terms = analysis["search_terms"]["web_search"]
9 task = asyncio.create_task(
10 self._perform_web_research(search_terms)
11 )
12 tasks.append(("web_search", task))
13
14 elif source == "arxiv" and "arxiv" in analysis["search_terms"]:
15 arxiv_query = analysis["search_terms"]["arxiv"]
16 categories = analysis.get("arxiv_categories", None)
17 task = asyncio.create_task(
18 self._perform_arxiv_research(arxiv_query, categories)
19 )
20 tasks.append(("arxiv", task))
21
22 elif source == "wikipedia" and "wikipedia" in analysis["search_terms"]:
23 wiki_query = analysis["search_terms"]["wikipedia"]
24 task = asyncio.create_task(
25 self._perform_wikipedia_research(wiki_query)
26 )
27 tasks.append(("wikipedia", task))
28
29 # Wait for all tasks to complete
30 results = []
31 for source_name, task in tasks:
32 try:
33 data = await task
34 results.append({
35 "source": source_name,
36 "data": data
37 })
38 except Exception as e:
39 print(f"Error researching {source_name}: {e}")
40 results.append({
41 "source": source_name,
42 "error": str(e)
43 })
44
45 # If needed, perform follow-up research based on initial findings
46 if analysis.get("requires_followup", False):
47 followup_results = await self._perform_followup_research(results, original_query)
48 results.extend(followup_results)
49
50 return results

Smart Throttling

Prevent overloading external services:

python
1class RateLimiter:
2 def __init__(self):
3 """Initialize rate limiters for different domains."""
4 self.limiters = {}
5
6 def register_domain(self, domain, requests_per_minute):
7 """Register rate limits for a domain."""
8 self.limiters[domain] = {
9 "rate": requests_per_minute,
10 "tokens": requests_per_minute,
11 "last_update": time.time(),
12 "lock": asyncio.Lock()
13 }
14
15 async def acquire(self, url):
16 """Acquire permission to make a request to a URL."""
17 domain = self._extract_domain(url)
18
19 if domain not in self.limiters:
20 # Default conservative limit
21 self.register_domain(domain, 10)
22
23 limiter = self.limiters[domain]
24
25 async with limiter["lock"]:
26 # Refill tokens based on time elapsed
27 now = time.time()
28 time_passed = now - limiter["last_update"]
29 new_tokens = time_passed * (limiter["rate"] / 60.0)
30 limiter["tokens"] = min(limiter["rate"], limiter["tokens"] + new_tokens)
31 limiter["last_update"] = now
32
33 if limiter["tokens"] < 1:
34 # Calculate wait time until a token is available
35 wait_time = (1 - limiter["tokens"]) * (60.0 / limiter["rate"])
36 await asyncio.sleep(wait_time)
37 limiter["tokens"] = 1
38 limiter["last_update"] = time.time()
39
40 # Consume a token
41 limiter["tokens"] -= 1
42
43 def _extract_domain(self, url):
44 """Extract the domain from a URL."""
45 parsed = urllib.parse.urlparse(url)
46 return parsed.netloc

Advanced technique: These optimizations show how to balance speed and resource usage. Caching prevents redundant research, parallel processing maximizes throughput, and rate limiting ensures respectful use of external services.


10. Security and Ethical Considerations

Securing Your Knowledge Companion

Implement these security measures to protect your system and users:

  1. Input validation: Sanitize all user inputs to prevent injection attacks
  2. Rate limiting: Prevent abuse by limiting requests per user
  3. Authentication: Require user authentication for sensitive operations
  4. Secure storage: Encrypt sensitive data and API keys
  5. Audit logging: Track all system actions for review

Example implementation of authentication middleware:

python
1from fastapi import Depends, HTTPException, status
2from fastapi.security import OAuth2PasswordBearer
3import jwt
4from datetime import datetime, timedelta
5
6# Setup
7oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
8SECRET_KEY = "your-secret-key" # Store securely in environment variables
9ALGORITHM = "HS256"
10
11# User authentication
12async def get_current_user(token: str = Depends(oauth2_scheme)):
13 credentials_exception = HTTPException(
14 status_code=status.HTTP_401_UNAUTHORIZED,
15 detail="Invalid authentication credentials",
16 headers={"WWW-Authenticate": "Bearer"},
17 )
18
19 try:
20 payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
21 username: str = payload.get("sub")
22 if username is None:
23 raise credentials_exception
24 except jwt.PyJWTError:
25 raise credentials_exception
26
27 # Get user from database (simplified)
28 user = get_user(username)
29 if user is None:
30 raise credentials_exception
31
32 return user
33
34# Example protected endpoint
35@app.post("/research/start", response_model=ResearchStatus)
36async def start_research(
37 query: Query,
38 background_tasks: BackgroundTasks,
39 current_user = Depends(get_current_user)
40):
41 # Check user permissions
42 if not user_can_research(current_user):
43 raise HTTPException(
44 status_code=status.HTTP_403_FORBIDDEN,
45 detail="Not enough permissions"
46 )
47
48 # Continue with research...

Ethical Web Scraping

Follow these guidelines for responsible web navigation:

  1. Respect robots.txt: Check for permission before crawling
  2. Identify your bot: Set proper User-Agent strings
  3. Rate limiting: Don't overload websites with requests
  4. Cache results: Minimize duplicate requests
  5. Honor copyright: Respect terms of service and licensing

Implementation example:

python
1class EthicalBrowser(IntelligentBrowser):
2 def __init__(self, headless=True, user_agent=None):
3 """Initialize with ethical browsing capabilities."""
4 super().__init__(headless)
5
6 # Set an honest user agent
7 if user_agent is None:
8 user_agent = "KnowledgeCompanionBot/1.0 (+https://yourwebsite.com/bot.html)"
9 self.browser.set_user_agent(user_agent)
10
11 # Initialize robots.txt cache
12 self.robots_cache = {}
13 self.rate_limiter = RateLimiter()
14
15 async def visit_page(self, url):
16 """Ethically visit a page with proper checks."""
17 # Check robots.txt first
18 domain = self._extract_domain(url)
19 if not await self._can_access(url):
20 return {
21 "url": url,
22 "error": "Access disallowed by robots.txt",
23 "content": None
24 }
25
26 # Apply rate limiting
27 await self.rate_limiter.acquire(url)
28
29 # Now perform the visit
30 return await super().visit_page(url)
31
32 async def _can_access(self, url):
33 """Check if a URL can be accessed according to robots.txt."""
34 domain = self._extract_domain(url)
35
36 # Check cache first
37 if domain in self.robots_cache:
38 parser = self.robots_cache[domain]["parser"]
39 last_checked = self.robots_cache[domain]["time"]
40
41 # Refresh cache if older than 1 day
42 if time.time() - last_checked > 86400:
43 parser = await self._fetch_robots_txt(domain)
44
45 else:
46 # Fetch and parse robots.txt
47 parser = await self._fetch_robots_txt(domain)
48
49 # Check if our user agent can access the URL
50 user_agent = self.browser.get_user_agent()
51 path = urllib.parse.urlparse(url).path
52 return parser.can_fetch(user_agent, path)
53
54 async def _fetch_robots_txt(self, domain):
55 """Fetch and parse robots.txt for a domain."""
56 robots_url = f"https://{domain}/robots.txt"
57
58 # Use a simple GET request, not the browser
59 async with aiohttp.ClientSession() as session:
60 try:
61 async with session.get(robots_url) as response:
62 if response.status == 200:
63 content = await response.text()
64 parser = robotparser.RobotFileParser()
65 parser.parse(content.splitlines())
66 else:
67 # No robots.txt or can't access it - create a permissive parser
68 parser = robotparser.RobotFileParser()
69 parser.allow_all = True
70 except:
71 # Error accessing robots.txt - create a permissive parser
72 parser = robotparser.RobotFileParser()
73 parser.allow_all = True
74
75 # Cache the result
76 self.robots_cache[domain] = {
77 "parser": parser,
78 "time": time.time()
79 }
80
81 return parser
82
83 def _extract_domain(self, url):
84 """Extract domain from URL."""
85 parsed = urllib.parse.urlparse(url)
86 return parsed.netloc

Ethical principle: This implementation demonstrates the technical aspects of ethical web scraping by checking robots.txt files, using honest user agents, and implementing rate limiting to be a good citizen of the web.


11. Testing and Quality Assurance

Unit Testing Core Components

Example of testing the Browser-Use module:

python
1import unittest
2from unittest.mock import MagicMock, patch
3import asyncio
4
5class TestIntelligentBrowser(unittest.TestCase):
6 @patch('browser_use.BrowserSession')
7 def setUp(self, MockBrowserSession):
8 self.mock_browser = MockBrowserSession.return_value
9 self.intelligent_browser = IntelligentBrowser(headless=True)
10
11 def test_search(self):
12 # Set up mocks
13 self.mock_browser.find_element.return_value = "search_box"
14 self.intelligent_browser.get_search_results = MagicMock(return_value=[
15 {"title": "Test Result", "url": "https://example.com", "snippet": "Example snippet"}
16 ])
17
18 # Execute search
19 results = self.intelligent_browser.search("test query")
20
21 # Verify
22 self.mock_browser.navigate.assert_called_with("https://www.google.com")
23 self.mock_browser.input_text.assert_called_with("search_box", "test query")
24 self.mock_browser.press_enter.assert_called_once()
25 self.assertEqual(len(results), 1)
26 self.assertEqual(results[0]["title"], "Test Result")
27
28 def test_extract_main_content(self):
29 # Set up mocks for different scenarios
30 self.mock_browser.find_element.side_effect = [
31 None, # No article
32 None, # No main
33 "content_div" # Found .content
34 ]
35 self.mock_browser.get_text.return_value = "Extracted content"
36
37 # Execute
38 content = self.intelligent_browser.extract_main_content()
39
40 # Verify
41 self.assertEqual(content, "Extracted content")
42 self.assertEqual(self.mock_browser.find_element.call_count, 3)
43
44class TestKnowledgeManager(unittest.TestCase):
45 def setUp(self):
46 self.mock_mcp = MagicMock()
47 self.knowledge_manager = KnowledgeSourceManager(self.mock_mcp)
48
49 def test_query_source(self):
50 # Set up
51 self.knowledge_manager.available_sources = {
52 "test_source": {"description": "Test", "capabilities": [], "parameters": {}}
53 }
54 self.mock_mcp.query_context.return_value = {"result": "test_data"}
55
56 # Execute
57 result = self.knowledge_manager.query_source("test_source", {"param": "value"})
58
59 # Verify
60 self.mock_mcp.query_context.assert_called_with(
61 context_name="test_source",
62 parameters={"param": "value"}
63 )
64 self.assertEqual(result, {"result": "test_data"})
65
66 def test_query_unknown_source(self):
67 # Verify exception for unknown source
68 with self.assertRaises(ValueError):
69 self.knowledge_manager.query_source("unknown_source", {})

Integration Testing

Test how components work together:

python
1class TestOrchestration(unittest.IsolatedAsyncioTestCase):
2 async def asyncSetUp(self):
3 # Create mocks for all components
4 self.mock_llm = MagicMock()
5 self.mock_browser = MagicMock()
6 self.mock_knowledge_manager = MagicMock()
7
8 # Setup orchestrator with mocks
9 self.orchestrator = KnowledgeOrchestrator(
10 self.mock_llm,
11 self.mock_browser,
12 self.mock_knowledge_manager
13 )
14
15 # Setup common test data
16 self.mock_llm.complete.return_value = json.dumps({
17 "priority_order": ["web_search", "arxiv"],
18 "search_terms": {
19 "web_search": ["test query"],
20 "arxiv": "test query physics"
21 },
22 "requires_followup": False
23 })
24
25 async def test_full_query_processing(self):
26 # Mock the research methods
27 self.orchestrator._perform_web_research = MagicMock(
28 return_value=asyncio.Future()
29 )
30 self.orchestrator._perform_web_research.return_value.set_result([
31 {"title": "Web Result", "url": "https://example.com"}
32 ])
33
34 self.mock_knowledge_manager.search_arxiv.return_value = {
35 "papers": [{"title": "ArXiv Paper", "abstract": "Test abstract"}]
36 }
37
38 # Mock report synthesis
39 final_report = "# Research Report\n\nThis is a test report."
40 self.mock_llm.complete.side_effect = [
41 # First call - query analysis
42 json.dumps({
43 "priority_order": ["web_search", "arxiv"],
44 "search_terms": {
45 "web_search": ["test query"],
46 "arxiv": "test query physics"
47 },
48 "requires_followup": False
49 }),
50 # Second call - report synthesis
51 final_report
52 ]
53
54 # Execute full query processing
55 result = await self.orchestrator.process_query("test query")
56
57 # Verify
58 self.assertEqual(result, final_report)
59 self.assertEqual(self.mock_llm.complete.call_count, 2)
60 self.orchestrator._perform_web_research.assert_called_once()
61 self.mock_knowledge_manager.search_arxiv.assert_called_once()

End-to-End Testing

Test the entire system with real inputs:

python
1class TestEndToEnd(unittest.IsolatedAsyncioTestCase):
2 async def asyncSetUp(self):
3 # Create real components with test configuration
4 self.llm_client = LlamaClient(model="llama3.2-test")
5 self.browser = IntelligentBrowser(headless=True)
6 self.mcp_client = MCPClient(
7 server_url="https://test-mcp.example.com",
8 api_key="test_key"
9 )
10 self.knowledge_manager = KnowledgeSourceManager(self.mcp_client)
11
12 # Set up the orchestrator with real components
13 self.orchestrator = KnowledgeOrchestrator(
14 self.llm_client,
15 self.browser,
16 self.knowledge_manager
17 )
18
19 # Create the CLI interface
20 self.cli = KnowledgeCompanionCLI(self.orchestrator)
21
22 async def asyncTearDown(self):
23 # Clean up resources
24 self.browser.close()
25
26 @unittest.skip("End-to-end test requires real services")
27 async def test_basic_query(self):
28 """Test end-to-end with a simple query."""
29 # This test is skipped by default as it requires real services
30 query = "What is the capital of France?"
31 report = await self.orchestrator.process_query(query)
32
33 # Verify basic expectations about the report
34 self.assertIn("Paris", report)
35 self.assertIn("France", report)
36 self.assertGreater(len(report), 100) # Ensure substantial content

Testing principle: The test suite demonstrates how to test at multiple levels—unit tests for individual functions, integration tests for component interactions, and end-to-end tests for the complete system. Note the use of mocks to isolate components during testing.


12. Deployment Strategies

Docker Containerization

Create a Dockerfile for your Knowledge Companion:

dockerfile
1# Use a Python base image
2FROM python:3.9-slim
3
4# Install system dependencies including Chrome/Chromium
5RUN apt-get update && apt-get install -y \
6 wget \
7 gnupg \
8 unzip \
9 && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
10 && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list \
11 && apt-get update \
12 && apt-get install -y google-chrome-stable \
13 && rm -rf /var/lib/apt/lists/*
14
15# Install ChromeDriver
16RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` && \
17 wget -N chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip -P ~/ && \
18 unzip ~/chromedriver_linux64.zip -d ~/ && \
19 rm ~/chromedriver_linux64.zip && \
20 mv -f ~/chromedriver /usr/local/bin/chromedriver && \
21 chmod +x /usr/local/bin/chromedriver
22
23# Set up workdir and install Python dependencies
24WORKDIR /app
25COPY requirements.txt .
26RUN pip install --no-cache-dir -r requirements.txt
27
28# Download and configure Ollama (for local LLM support)
29RUN wget -O ollama https://ollama.ai/download/ollama-linux-amd64 && \
30 chmod +x ollama && \
31 mv ollama /usr/local/bin/
32
33# Copy application code
34COPY . .
35
36# Create a non-root user
37RUN useradd -m appuser
38USER appuser
39
40# Set environment variables
41ENV PYTHONUNBUFFERED=1
42ENV BROWSER_USE_HEADLESS=true
43ENV OLLAMA_HOST=host.docker.internal
44
45# Expose port for the API
46EXPOSE 8000
47
48# Health check
49HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
50 CMD curl -f http://localhost:8000/health || exit 1
51
52# Command to run the application
53CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Using Docker Compose for Multi-Container Deployment

Create a docker-compose.yml file:

yaml
1version: '3.8'
2
3services:
4 # API Service
5 api:
6 build: .
7 ports:
8 - "8000:8000"
9 environment:
10 - REDIS_URL=redis://redis:6379
11 - MCP_SERVER_URL=http://mcp:5000
12 depends_on:
13 - redis
14 - mcp
15 - ollama
16 volumes:
17 - ./app:/app
18 networks:
19 - knowledge-network
20
21 # Redis for caching
22 redis:
23 image: redis:6.2-alpine
24 ports:
25 - "6379:6379"
26 volumes:
27 - redis-data:/data
28 command: redis-server --appendonly yes
29 networks:
30 - knowledge-network
31
32 # MCP Server
33 mcp:
34 build: ./mcp-server
35 ports:
36 - "5000:5000"
37 environment:
38 - PYTHONUNBUFFERED=1
39 volumes:
40 - ./mcp-server:/app
41 networks:
42 - knowledge-network
43
44 # Ollama for local LLM support
45 ollama:
46 image: ollama/ollama:latest
47 volumes:
48 - ollama-data:/root/.ollama
49 ports:
50 - "11434:11434"
51 networks:
52 - knowledge-network
53
54networks:
55 knowledge-network:
56 driver: bridge
57
58volumes:
59 redis-data:
60 ollama-data:

Serverless Deployment

For AWS Lambda deployment, create a serverless.yml configuration:

yaml
1service: knowledge-companion
2
3provider:
4 name: aws
5 runtime: python3.9
6 region: us-east-1
7 memorySize: 2048
8 timeout: 30
9 environment:
10 MCP_SERVER_URL: ${env:MCP_SERVER_URL}
11 REDIS_URL: ${env:REDIS_URL}
12
13functions:
14 api:
15 handler: serverless_handler.handler
16 events:
17 - http:
18 path: /
19 method: ANY
20 - http:
21 path: /{proxy+}
22 method: ANY
23 layers:
24 - !Ref PythonDependenciesLambdaLayer
25
26layers:
27 pythonDependencies:
28 path: layer
29 compatibleRuntimes:
30 - python3.9
31
32custom:
33 pythonRequirements:
34 dockerizePip: true
35 slim: true
36 layer: true
37
38plugins:
39 - serverless-python-requirements
40 - serverless-offline

Deployment concept: These examples show multiple deployment strategies—containerized for easy distribution, Docker Compose for multi-service orchestration, and serverless for scalable API deployment. The actual choice depends on your specific requirements and infrastructure constraints.


13. Practical Applications and Use Cases

Academic Research Assistant

Customize your Knowledge Companion for academic research:

python
1class AcademicResearchOrchestrator(KnowledgeOrchestrator):
2 """Specialized orchestrator for academic research."""
3
4 async def _analyze_query(self, query):
5 """Academic-focused query analysis."""
6 prompt = f"""
7 Analyze the following academic research query and determine the best approach:
8
9 QUERY: {query}
10
11 Please determine:
12 1. Which academic fields are most relevant to this query?
13 2. What specific academic databases should be consulted? (arXiv, PubMed, etc.)
14 3. What are the key search terms for academic literature?
15 4. Are there specific authors or institutions known for work in this area?
16 5. What time period is most relevant for this research?
17
18 Return your analysis as a structured JSON object.
19 """
20
21 response = await self.llm.complete(prompt)
22 return json.loads(response)
23
24 async def _perform_literature_review(self, analysis):
25 """Conduct a comprehensive literature review."""
26 sources = []
27
28 # Search across multiple academic databases
29 if "arxiv" in analysis.get("databases", []):
30 arxiv_results = await self._search_arxiv(
31 analysis["search_terms"],
32 analysis.get("categories", [])
33 )
34 sources.append(("arxiv", arxiv_results))
35
36 if "pubmed" in analysis.get("databases", []):
37 pubmed_results = await self._search_pubmed(
38 analysis["search_terms"],
39 analysis.get("date_range", {})
40 )
41 sources.append(("pubmed", pubmed_results))
42
43 # Find and analyze citation networks
44 key_papers = self._identify_key_papers(sources)
45 citation_network = await self._analyze_citations(key_papers)
46
47 return {
48 "primary_sources": sources,
49 "key_papers": key_papers,
50 "citation_network": citation_network
51 }
52
53 async def _generate_academic_report(self, query, research_results):
54 """Generate an academic-style report with proper citations."""
55 prompt = f"""
56 Create a comprehensive academic literature review based on the following research:
57
58 QUERY: {query}
59
60 RESEARCH FINDINGS: {json.dumps(research_results, indent=2)}
61
62 Your review should:
63 1. Begin with an abstract summarizing the findings
64 2. Include a methodology section explaining the search strategy
65 3. Present findings organized by themes or chronologically
66 4. Discuss conflicts and agreements in the literature
67 5. Identify research gaps
68 6. Include a properly formatted bibliography (APA style)
69
70 Format the review in Markdown.
71 """
72
73 review = await self.llm.complete(prompt)
74 return review

Business Intelligence Tool

Adapt your Knowledge Companion for business intelligence:

python
1class BusinessIntelligenceOrchestrator(KnowledgeOrchestrator):
2 """Specialized orchestrator for business intelligence."""
3
4 async def _analyze_query(self, query):
5 """Business-focused query analysis."""
6 prompt = f"""
7 Analyze the following business intelligence query and determine the best approach:
8
9 QUERY: {query}
10
11 Please determine:
12 1. What industry sectors are relevant to this query?
13 2. What specific companies should be researched?
14 3. What types of business data would be most valuable (financial, strategic, competitive)?
15 4. What time frame is relevant for this analysis?
16 5. What business news sources would be most appropriate?
17
18 Return your analysis as a structured JSON object.
19 """
20
21 response = await self.llm.complete(prompt)
22 return json.loads(response)
23
24 async def _gather_company_data(self, companies):
25 """Gather data about specific companies."""
26 results = []
27
28 for company in companies:
29 # Financial data
30 financial_data = await self._fetch_financial_data(company)
31
32 # News and press releases
33 news = await self._fetch_company_news(company)
34
35 # Social media sentiment
36 sentiment = await self._analyze_social_sentiment(company)
37
38 results.append({
39 "company": company,
40 "financial": financial_data,
41 "news": news,
42 "sentiment": sentiment
43 })
44
45 return results
46
47 async def _perform_competitive_analysis(self, primary_company, competitors):
48 """Analyze competitive positioning."""
49 company_data = await self._gather_company_data([primary_company] + competitors)
50
51 # Extract the primary company data
52 primary_data = next(item for item in company_data if item["company"] == primary_company)
53
54 # Extract competitor data
55 competitor_data = [item for item in company_data if item["company"] != primary_company]
56
57 # Perform SWOT analysis
58 swot = await self._generate_swot_analysis(primary_data, competitor_data)
59
60 return {
61 "primary_company": primary_data,
62 "competitors": competitor_data,
63 "swot_analysis": swot
64 }
65
66 async def _generate_business_report(self, query, intelligence_data):
67 """Generate a business intelligence report."""
68 prompt = f"""
69 Create a comprehensive business intelligence report based on the following data:
70
71 QUERY: {query}
72
73 INTELLIGENCE DATA: {json.dumps(intelligence_data, indent=2)}
74
75 Your report should:
76 1. Begin with an executive summary
77 2. Include market overview and trends
78 3. Provide detailed company analyses
79 4. Present competitive comparisons with data visualizations (described in text)
80 5. Offer strategic recommendations
81 6. Include reference sources
82
83 Format the report in Markdown with sections for easy navigation.
84 """
85
86 report = await self.llm.complete(prompt)
87 return report

Personal Learning Assistant

Create a personal learning companion:

python
1class LearningPathOrchestrator(KnowledgeOrchestrator):
2 """Specialized orchestrator for creating personalized learning paths."""
3
4 async def create_learning_path(self, subject, user_level="beginner", goals=None):
5 """Create a personalized learning path for a subject."""
6 analysis = await self._analyze_learning_needs(subject, user_level, goals)
7 resources = await self._discover_learning_resources(analysis)
8 curriculum = await self._design_curriculum(analysis, resources)
9
10 return {
11 "subject": subject,
12 "user_level": user_level,
13 "goals": goals,
14 "curriculum": curriculum
15 }
16
17 async def _analyze_learning_needs(self, subject, user_level, goals):
18 """Analyze the learning requirements for a subject."""
19 prompt = f"""
20 Analyze the following learning request and determine the optimal approach:
21
22 SUBJECT: {subject}
23 USER LEVEL: {user_level}
24 LEARNING GOALS: {goals if goals else 'General proficiency'}
25
26 Please determine:
27 1. What are the foundational concepts needed for this subject?
28 2. What is an appropriate learning sequence?
29 3. What types of resources would be most beneficial (textbooks, videos, interactive exercises)?
30 4. How should progress be measured?
31 5. What are common stumbling blocks for learners at this level?
32
33 Return your analysis as a structured JSON object.
34 """
35
36 response = await self.llm.complete(prompt)
37 return json.loads(response)
38
39 async def _discover_learning_resources(self, analysis):
40 """Find optimal learning resources based on analysis."""
41 resources = {
42 "textbooks": [],
43 "online_courses": [],
44 "videos": [],
45 "practice_resources": [],
46 "communities": []
47 }
48
49 # Search for textbooks and books
50 book_results = await self._search_books(analysis["subject"], analysis["key_concepts"])
51 resources["textbooks"] = book_results[:5] # Top 5 relevant books
52
53 # Search for online courses
54 course_results = await self._search_online_courses(
55 analysis["subject"],
56 analysis["user_level"]
57 )
58 resources["online_courses"] = course_results[:5]
59
60 # Find educational videos
61 video_results = await self._search_educational_videos(
62 analysis["subject"],
63 analysis["key_concepts"]
64 )
65 resources["videos"] = video_results[:8]
66
67 # Find practice resources
68 practice_results = await self._search_practice_resources(analysis["subject"])
69 resources["practice_resources"] = practice_results[:5]
70
71 # Find learning communities
72 community_results = await self._search_learning_communities(analysis["subject"])
73 resources["communities"] = community_results[:3]
74
75 return resources
76
77 async def _design_curriculum(self, analysis, resources):
78 """Design a structured curriculum based on analysis and resources."""
79 prompt = f"""
80 Create a comprehensive learning curriculum based on the following:
81
82 SUBJECT ANALYSIS: {json.dumps(analysis, indent=2)}
83
84 AVAILABLE RESOURCES: {json.dumps(resources, indent=2)}
85
86 Your curriculum should:
87 1. Be organized in modules with clear learning objectives for each
88 2. Include a mix of theory and practice
89 3. Estimate time commitments for each module
90 4. Recommend specific resources for each module
91 5. Include checkpoints to assess understanding
92 6. Provide a progression from foundational to advanced concepts
93
94 Format the curriculum in Markdown with clear sections.
95 """
96
97 curriculum = await self.llm.complete(prompt)
98 return curriculum

Application principle: These specialized orchestrators show how the same core technology can be adapted to different domains by modifying the analysis, research strategies, and report generation based on domain-specific requirements.


14. Conclusion: The Future of AI Knowledge Systems

The AI Knowledge Companion we've built represents just the beginning of a new era in information processing. As we look to the future, several trends are emerging:

  1. Deeper reasoning capabilities: Future systems will go beyond information retrieval to perform logical reasoning, connecting disparate pieces of information to generate novel insights.

  2. Multimodal understanding: The next generation of knowledge systems will process not just text but images, audio, and video seamlessly, extracting information from all media types.

  3. Collective intelligence: Knowledge companions will facilitate collaboration between multiple human experts and AI systems, creating hybrid intelligence networks.

  4. Continuous learning: Systems will remember past interactions and continuously refine their understanding based on user feedback and new information.

  5. Specialized domain expertise: We'll see the rise of highly specialized companions focused on specific fields like medicine, law, or engineering.

The technologies we've explored—Browser-Use and MCP—are foundational pieces of this future, enabling AI systems to navigate the internet and connect to diverse knowledge sources in standardized ways. As these technologies mature, the boundary between AI assistants and knowledge workers will continue to blur, creating powerful partnerships that enhance human capabilities.

By building your own AI Knowledge Companion, you're not just creating a research tool—you're participating in the development of systems that will fundamentally transform how humans interact with the vast landscape of global knowledge.


Additional Resources

Happy knowledge exploration!

Sovereign AI book cover

Sovereign AI: Building Local-First Intelligent Systems

by Daniel Kliewer · Paperback · 72 pages

The hands-on guide to building AI that runs on your hardware, keeps your data private, and eliminates cloud dependence. Working code included.