"""
Pattern 118: Curiosity-Driven Exploration
Description:
Agent explores environment based on intrinsic motivation driven by
novelty, information gain, and prediction error.
Components:
- Novelty detection
- Information gain calculation
- Exploration strategy
- Intrinsic reward
Use Cases:
- Open-ended learning
- Exploration tasks
- Discovery systems
LangChain Implementation:
Uses LLM to generate curious questions and exploration strategies based on
knowledge gaps and novelty.
"""
import os
from typing import List, Dict, Any, Set
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
class CuriosityDrivenAgent:
"""Agent that explores based on intrinsic curiosity."""
def __init__(self, model_name: str = "gpt-4"):
self.llm = ChatOpenAI(model=model_name, temperature=0.8)
self.knowledge_base = set()
self.explored_topics = []
self.curiosity_log = []
def assess_novelty(self, topic: str) -> float:
"""Assess how novel a topic is based on existing knowledge."""
novelty_prompt = ChatPromptTemplate.from_messages([
("system", "Rate the novelty of this topic given existing knowledge. Return a score from 0.0 (completely known) to 1.0 (completely novel)."),
("user", """Topic: {topic}
Known Knowledge:
{knowledge}
Novelty score (0.0-1.0):""")
])
knowledge_str = "\n".join([f"- {k}" for k in list(self.knowledge_base)[:20]]) or "Empty"
chain = novelty_prompt | self.llm | StrOutputParser()
response = chain.invoke({"topic": topic, "knowledge": knowledge_str})
try:
score = float(response.strip())
return max(0.0, min(1.0, score))
except:
return 0.5
def generate_curious_questions(self, context: str) -> List[str]:
"""Generate questions driven by curiosity about knowledge gaps."""
curiosity_prompt = ChatPromptTemplate.from_messages([
("system", """You are a curious agent exploring knowledge.
Generate 3-5 interesting questions that would expand understanding in unexplored directions.
Focus on:
- Novel connections
- Surprising implications
- Unexplored aspects
- Deep mechanisms"""),
("user", """Current Context: {context}
Known Knowledge:
{knowledge}
Generate curious questions:""")
])
knowledge_str = "\n".join([f"- {k}" for k in list(self.knowledge_base)[-10:]]) or "Empty"
chain = curiosity_prompt | self.llm | StrOutputParser()
response = chain.invoke({"context": context, "knowledge": knowledge_str})
# Parse questions
questions = [line.strip('- ').strip() for line in response.split('\n') if line.strip()]
return [q for q in questions if '?' in q][:5]
def calculate_information_gain(self, potential_exploration: str) -> float:
"""Calculate expected information gain from exploring a topic."""
gain_prompt = ChatPromptTemplate.from_messages([
("system", "Estimate information gain (0.0-1.0) from exploring this topic given current knowledge."),
("user", """Potential Exploration: {exploration}
Current Knowledge:
{knowledge}
Information gain score (0.0-1.0):""")
])
knowledge_str = "\n".join([f"- {k}" for k in list(self.knowledge_base)[-15:]]) or "Empty"
chain = gain_prompt | self.llm | StrOutputParser()
response = chain.invoke({"exploration": potential_exploration, "knowledge": knowledge_str})
try:
score = float(response.strip())
return max(0.0, min(1.0, score))
except:
return 0.5
def explore(self, topic: str) -> Dict[str, Any]:
"""Explore a topic and gain knowledge."""
print(f"\nš Exploring: {topic}")
# Assess novelty
novelty = self.assess_novelty(topic)
print(f" Novelty: {novelty:.2f}")
# Calculate information gain
info_gain = self.calculate_information_gain(topic)
print(f" Information Gain: {info_gain:.2f}")
# Intrinsic reward
intrinsic_reward = (novelty + info_gain) / 2
print(f" Intrinsic Reward: {intrinsic_reward:.2f}")
# Explore the topic
exploration_prompt = ChatPromptTemplate.from_messages([
("system", "Explore this topic deeply and discover interesting insights."),
("user", "Topic: {topic}\n\nProvide interesting discoveries:")
])
chain = exploration_prompt | self.llm | StrOutputParser()
discoveries = chain.invoke({"topic": topic})
# Update knowledge base
self.knowledge_base.add(topic)
self.explored_topics.append(topic)
result = {
"topic": topic,
"novelty": novelty,
"information_gain": info_gain,
"intrinsic_reward": intrinsic_reward,
"discoveries": discoveries
}
self.curiosity_log.append(result)
print(f"\n Discoveries:")
for line in discoveries.split('\n')[:3]:
if line.strip():
print(f" ⢠{line.strip()}")
return result
def choose_next_exploration(self, candidates: List[str]) -> str:
"""Choose next exploration target based on curiosity."""
print("\nš Evaluating exploration candidates...")
scores = []
for candidate in candidates:
novelty = self.assess_novelty(candidate)
info_gain = self.calculate_information_gain(candidate)
curiosity_score = (novelty + info_gain) / 2
scores.append((candidate, curiosity_score))
print(f" {candidate}: {curiosity_score:.2f}")
# Choose highest curiosity score
best_candidate = max(scores, key=lambda x: x[1])
print(f"\n⨠Selected: {best_candidate[0]} (score: {best_candidate[1]:.2f})")
return best_candidate[0]
def get_exploration_summary(self) -> str:
"""Summarize exploration journey."""
summary_prompt = ChatPromptTemplate.from_messages([
("system", "Summarize this curiosity-driven exploration journey."),
("user", """Explored Topics:
{topics}
Curiosity Log:
{log}
Provide a summary of:
1. Key discoveries
2. Exploration patterns
3. Knowledge growth
4. Most rewarding explorations""")
])
topics = "\n".join([f"{i+1}. {t}" for i, t in enumerate(self.explored_topics)])
log = "\n".join([
f"Topic: {entry['topic']}, Reward: {entry['intrinsic_reward']:.2f}"
for entry in self.curiosity_log
])
chain = summary_prompt | self.llm | StrOutputParser()
return chain.invoke({"topics": topics, "log": log})
def demonstrate_curiosity_driven_exploration():
"""Demonstrate curiosity-driven exploration pattern."""
print("=== Curiosity-Driven Exploration Pattern ===\n")
agent = CuriosityDrivenAgent()
# Initial topic
print("1. Starting Exploration from Initial Topic")
print("-" * 50)
agent.explore("Quantum Computing")
# Generate curious questions
print("\n2. Generating Curious Questions")
print("-" * 50)
questions = agent.generate_curious_questions("Quantum Computing")
print("Curious questions generated:")
for i, q in enumerate(questions, 1):
print(f"{i}. {q}")
# Explore based on curiosity
print("\n3. Curiosity-Driven Exploration")
print("-" * 50)
# Candidate topics
candidates = [
"Quantum Entanglement Applications",
"Quantum Error Correction",
"Superconducting Qubits",
"Quantum Algorithms for Chemistry"
]
# Choose and explore
for i in range(3):
print(f"\n--- Exploration Round {i+1} ---")
next_topic = agent.choose_next_exploration(candidates)
agent.explore(next_topic)
# Remove explored topic and add related ones
candidates.remove(next_topic)
# Generate new candidates based on discoveries
if i < 2:
new_candidates_prompt = ChatPromptTemplate.from_messages([
("system", "Suggest 2 related unexplored topics based on recent discovery."),
("user", "Recent exploration: {topic}\n\nSuggest new topics:")
])
chain = new_candidates_prompt | agent.llm | StrOutputParser()
new_suggestions = chain.invoke({"topic": next_topic})
new_topics = [line.strip('- ').strip() for line in new_suggestions.split('\n') if line.strip()][:2]
candidates.extend(new_topics)
# Summary
print("\n4. Exploration Journey Summary")
print("-" * 50)
summary = agent.get_exploration_summary()
print(summary)
print("\n=== Summary ===")
print(f"Total explorations: {len(agent.explored_topics)}")
print(f"Knowledge base size: {len(agent.knowledge_base)}")
print(f"Average intrinsic reward: {sum(e['intrinsic_reward'] for e in agent.curiosity_log) / len(agent.curiosity_log):.2f}")
print("\nCuriosity-driven exploration demonstrated with:")
print("- Novelty assessment")
print("- Information gain calculation")
print("- Intrinsic reward-based decisions")
print("- Autonomous exploration strategy")
print("- Continuous knowledge expansion")
if __name__ == "__main__":
demonstrate_curiosity_driven_exploration()
Agentic Design Pattern: Curiosity Driven Exploration
Pattern 118: Curiosity Driven Exploration – Line-by-Line Explanation
Overview
This document provides a detailed line-by-line explanation of the Curiosity Driven Exploration pattern implementation.
Pattern Description
Pattern 118: Curiosity-Driven Exploration
Description: Agent explores environment based on intrinsic motivation driven by novelty, information gain, and prediction error.
Components: – Novelty detection – Information gain calculation – Exploration strategy – Intrinsic reward
Use Cases: – Open-ended learning – Exploration tasks – Discovery systems
LangChain Implementation: Uses LLM to generate curious questions and exploration strategies based on knowledge gaps and novelty.
File Statistics
- Total Lines: 279
- Classes: 1
- Functions: 1
- Constants: 0
Module Documentation
"""
Pattern 118: Curiosity-Driven Exploration
Description:
Agent explores environment based on intrinsic motivation driven by
novelty, information gain, and prediction error.
Components:
- Novelty detection
- Information gain calculation
- Exploration strategy
- Intrinsic reward
Use Cases:
- Open-ended learning
- Exploration tasks
- Discovery systems
LangChain Implementation:
Uses LLM to generate curious questions and exploration strategies based on
knowledge gaps and novelty.
"""
Purpose: This module implements the Curiosity Driven Exploration pattern, providing a structured approach to agent design and implementation.
Import Statements
Standard Library Imports
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import os
import typing.Any
import typing.Dict
import typing.List
import typing.Set
Purpose: Core Python functionality:
– langchain_core.output_parsers.StrOutputParser: Regular expression operations
– langchain_core.prompts.ChatPromptTemplate: Regular expression operations
– os: Environment variable access and system operations
– typing.Any: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.List: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.Set: Type hints for better code documentation (List, Dict, Any, Optional)
LangChain Imports
from langchain_openai.ChatOpenAI
Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows
Other Imports
from dotenv.load_dotenv
Purpose: Additional dependencies for specialized functionality.
Classes
Class: CuriosityDrivenAgent
Lines 35-205
Purpose:
Agent that explores based on intrinsic curiosity.
Methods:
__init__(self, model_name)
Lines 38-42
Purpose: Initialize the class instance with required parameters.
Parameters:
– model_name: Language model instance
assess_novelty(self, topic)
Lines 44-65
Purpose:
Assess how novel a topic is based on existing knowledge.
Parameters:
– topic: Topic
Returns: float
generate_curious_questions(self, context)
Lines 67-92
Purpose:
Generate questions driven by curiosity about knowledge gaps.
Parameters:
– context: Context
Returns: <ast.Subscript object at 0x000001A06D7CC0D0>
calculate_information_gain(self, potential_exploration)
Lines 94-115
Purpose:
Calculate expected information gain from exploring a topic.
Parameters:
– potential_exploration: Potential exploration
Returns: float
explore(self, topic)
Lines 117-161
Purpose:
Explore a topic and gain knowledge.
Parameters:
– topic: Topic
Returns: <ast.Subscript object at 0x000001A06D88B210>
choose_next_exploration(self, candidates)
Lines 163-179
Purpose:
Choose next exploration target based on curiosity.
Parameters:
– candidates: Candidates
Returns: str
get_exploration_summary(self)
Lines 181-205
Purpose:
Summarize exploration journey.
Parameters:
Returns: str
Functions
demonstrate_curiosity_driven_exploration()
Lines 208-274
Purpose:
Demonstrate curiosity-driven exploration pattern.
Key Concepts Summary
Pattern Overview
The Curiosity Driven Exploration pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.
Core Components
- Agent Class: Main implementation of the pattern logic
- Prompt Engineering: Carefully crafted prompts to guide LLM behavior
- Tool Integration: Optional tools for extended functionality
- Memory Management: State handling across interactions
- Output Parsing: Structured response processing
When to Use
ā Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability
ā Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)
Best Practices Demonstrated
1. Environment Configuration
- ā
Use
.envfiles for API keys - ā Never hardcode credentials
- ā Validate environment variables on startup
2. Error Handling
- ā Graceful degradation on failures
- ā Informative error messages
- ā Logging for debugging
3. Prompt Engineering
- ā Clear, specific instructions
- ā Structured output formats
- ā Few-shot examples when beneficial
4. Code Organization
- ā Separation of concerns (class-based design)
- ā Type hints for clarity
- ā Docstrings for documentation
5. Performance Optimization
- ā Appropriate temperature settings
- ā Token budget management
- ā Caching where applicable
LangChain Components Used
- ChatOpenAI: OpenAI model interface
- Chains: Sequential component composition
Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development
Execution Flow
1. Initialize agent with configuration
ā
2. Load environment and dependencies
ā
3. Prepare prompt with input data
ā
4. Invoke LLM through LangChain
ā
5. Parse and validate response
ā
6. Return formatted result
Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation
Performance Considerations
Computational Cost
- LLM Calls: Primary cost factor (based on tokens)
- Latency: Depends on model and input size
- Memory: Minimal for most patterns
Optimization Strategies
- Prompt Optimization: Minimize tokens while maintaining clarity
- Caching: Cache repeated queries/responses
- Model Selection: Use smaller models when appropriate
- Batch Processing: Group similar requests
- Streaming: For long responses, stream tokens
Cost Estimates (Approximate)
- GPT-3.5-turbo: ~$0.002 per request
- GPT-4: ~$0.03 per request
- GPT-4-turbo: ~$0.01 per request
Actual costs vary based on input/output token counts
Common Pitfalls to Avoid
1. Prompt Design Issues
ā Vague or ambiguous instructions
ā
Clear, specific, testable prompts
2. Error Handling
ā Ignoring API errors or rate limits
ā
Implement retry logic and graceful degradation
3. Token Management
ā Exceeding model token limits
ā
Truncate inputs, chunk processing
4. Security
ā Exposing API keys in code
ā
Use environment variables
5. Testing
ā Only testing happy paths
ā
Test edge cases, failures, malformed inputs
6. Cost Management
ā Unmonitored API usage
ā
Implement budgets, alerts, and monitoring
Further Improvements
Production Enhancements
- Monitoring: Add logging, metrics, tracing
- Validation: Input/output schema validation
- Rate Limiting: Implement request throttling
- Caching: Redis or in-memory cache for responses
- Async Support: Non-blocking operations for scalability
Feature Extensions
- Multi-Model Support: Switch between providers
- Fallback Models: Graceful degradation strategy
- Custom Tools: Domain-specific function calling
- Advanced Memory: Vector stores for long-term memory
- Evaluation: Automated testing and benchmarking
Code Quality
- Testing: Unit tests, integration tests
- Documentation: API docs, usage examples
- Type Safety: Stricter type hints
- Configuration: Externalized config files
Related Patterns
- Pattern 117
- Pattern 119
Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…
Summary
The Curiosity Driven Exploration pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:
ā Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns
šÆ Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability
š Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/
File: 118_curiosity_driven_exploration.py
Total Lines: 279
Pattern Category: AI Agent Design Pattern #118
Generated from actual implementation code
For educational and reference purposes
Leave a Reply