use std::future::Future;
use std::pin::Pin;
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use super::{ThinkToolContext, ThinkToolModule, ThinkToolModuleConfig, ThinkToolOutput};
use crate::error::{Error, Result};
use rayon::prelude::*;
#[derive(Error, Debug, Clone)]
pub enum GigaThinkError {
#[error("Insufficient perspectives: generated {generated}, required minimum {required}")]
InsufficientPerspectives { generated: usize, required: usize },
#[error("Invalid analysis dimension: {dimension}")]
InvalidDimension { dimension: String },
#[error("Query too short: {length} characters, minimum required is {minimum}")]
QueryTooShort { length: usize, minimum: usize },
#[error("Query too long: {length} characters, maximum allowed is {maximum}")]
QueryTooLong { length: usize, maximum: usize },
#[error("Confidence too low: {confidence:.2}, minimum required is {threshold:.2}")]
LowConfidence { confidence: f64, threshold: f64 },
#[error("Cross-validation failed: {reason}")]
CrossValidationFailed { reason: String },
#[error("Failed to synthesize perspectives: {reason}")]
SynthesisFailed { reason: String },
#[error("Execution timeout after {duration_ms}ms")]
ExecutionTimeout { duration_ms: u64 },
}
impl From<GigaThinkError> for Error {
fn from(err: GigaThinkError) -> Self {
Error::ThinkToolExecutionError(err.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningMode {
#[default]
Standard,
ChainOfDraft,
Adaptive,
}
impl ReasoningMode {
pub fn system_prompt_modifier(&self) -> &'static str {
match self {
Self::Standard => "",
Self::ChainOfDraft => {
"Think step by step, but keep each intermediate step to ~5 words maximum. \
Focus on essential reasoning only. Mark your final answer with ####."
}
Self::Adaptive => {
"Start with brief (~5 word) reasoning steps. If uncertain, expand that step. \
Mark your final answer with ####."
}
}
}
pub fn answer_marker(&self) -> &'static str {
match self {
Self::Standard => "",
Self::ChainOfDraft | Self::Adaptive => "####",
}
}
pub fn is_token_efficient(&self) -> bool {
matches!(self, Self::ChainOfDraft | Self::Adaptive)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChainOfDraftConfig {
pub words_per_step: usize,
pub strict_word_budget: bool,
pub answer_marker: String,
pub include_step_numbers: bool,
pub adaptive_expansion_threshold: f64,
}
impl Default for ChainOfDraftConfig {
fn default() -> Self {
Self {
words_per_step: 5,
strict_word_budget: false,
answer_marker: "####".to_string(),
include_step_numbers: true,
adaptive_expansion_threshold: 0.7,
}
}
}
pub struct ChainOfDraftUtils;
impl ChainOfDraftUtils {
pub fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
pub fn exceeds_word_budget(step: &str, budget: usize) -> bool {
Self::count_words(step) > budget
}
pub fn extract_answer(output: &str, marker: &str) -> Option<String> {
if marker.is_empty() {
return None;
}
output.split(marker).nth(1).map(|s| s.trim().to_string())
}
pub fn parse_draft_steps(output: &str, marker: &str) -> Vec<String> {
let reasoning_part = if marker.is_empty() {
output
} else {
output.split(marker).next().unwrap_or(output)
};
reasoning_part
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix(|c: char| c.is_ascii_digit()) {
rest.trim_start_matches(['.', ':', ')']).trim().to_string()
} else {
trimmed.to_string()
}
})
.collect()
}
pub fn estimate_token_efficiency(draft_words: usize, estimated_full_cot_words: usize) -> f64 {
if estimated_full_cot_words == 0 {
return 1.0;
}
draft_words as f64 / estimated_full_cot_words as f64
}
pub fn generate_cod_prompt(query: &str, words_per_step: usize) -> String {
format!(
"Question: {}\n\n\
Instructions: Think step by step to solve this problem. \
Keep each reasoning step to approximately {} words. \
Focus on the essential logic only. \
After your reasoning, write #### followed by your final answer.\n\n\
Reasoning:",
query, words_per_step
)
}
pub fn generate_dimension_cod_prompt(
query: &str,
dimension: &str,
guiding_question: &str,
words_per_step: usize,
) -> String {
format!(
"Analyze from {} perspective: {}\n\
Guiding question: {}\n\n\
Instructions: Provide brief insights (~{} words per point). \
Mark final insight with ####.\n\n\
Analysis:",
dimension, query, guiding_question, words_per_step
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChainOfDraftMetrics {
pub total_reasoning_words: usize,
pub step_count: usize,
pub avg_words_per_step: f64,
pub token_efficiency_ratio: f64,
pub steps_over_budget: usize,
pub adaptive_expansion_used: bool,
}
impl ChainOfDraftMetrics {
pub fn from_draft(output: &str, marker: &str, word_budget: usize) -> Self {
let steps = ChainOfDraftUtils::parse_draft_steps(output, marker);
let step_count = steps.len();
let total_words: usize = steps
.iter()
.map(|s| ChainOfDraftUtils::count_words(s))
.sum();
let steps_over: usize = steps
.iter()
.filter(|s| ChainOfDraftUtils::exceeds_word_budget(s, word_budget))
.count();
let estimated_full_cot = step_count * 50;
Self {
total_reasoning_words: total_words,
step_count,
avg_words_per_step: if step_count > 0 {
total_words as f64 / step_count as f64
} else {
0.0
},
token_efficiency_ratio: ChainOfDraftUtils::estimate_token_efficiency(
total_words,
estimated_full_cot,
),
steps_over_budget: steps_over,
adaptive_expansion_used: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigaThinkConfig {
pub reasoning_mode: ReasoningMode,
pub cod_config: ChainOfDraftConfig,
pub min_perspectives: usize,
pub max_perspectives: usize,
pub min_confidence: f64,
pub enable_cross_validation: bool,
pub min_query_length: usize,
pub max_query_length: usize,
pub dimensions: Vec<AnalysisDimension>,
pub novelty_weight: f64,
pub depth_weight: f64,
pub coherence_weight: f64,
pub max_execution_time_ms: Option<u64>,
}
impl Default for GigaThinkConfig {
fn default() -> Self {
Self {
reasoning_mode: ReasoningMode::default(),
cod_config: ChainOfDraftConfig::default(),
min_perspectives: 10,
max_perspectives: 15,
min_confidence: 0.70,
enable_cross_validation: true,
min_query_length: 10,
max_query_length: 5000,
dimensions: Vec::new(), novelty_weight: 0.30,
depth_weight: 0.40,
coherence_weight: 0.30,
max_execution_time_ms: Some(10000),
}
}
}
impl GigaThinkConfig {
pub fn chain_of_draft() -> Self {
Self {
reasoning_mode: ReasoningMode::ChainOfDraft,
cod_config: ChainOfDraftConfig::default(),
min_perspectives: 5,
max_perspectives: 8,
min_confidence: 0.65,
..Self::default()
}
}
pub fn adaptive() -> Self {
Self {
reasoning_mode: ReasoningMode::Adaptive,
cod_config: ChainOfDraftConfig {
adaptive_expansion_threshold: 0.7,
..ChainOfDraftConfig::default()
},
min_perspectives: 7,
max_perspectives: 12,
min_confidence: 0.70,
..Self::default()
}
}
pub fn with_reasoning_mode(mut self, mode: ReasoningMode) -> Self {
self.reasoning_mode = mode;
self
}
pub fn with_words_per_step(mut self, words: usize) -> Self {
self.cod_config.words_per_step = words;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnalysisDimension {
Economic,
Technological,
Social,
Environmental,
Political,
Psychological,
Ethical,
Historical,
Competitive,
UserExperience,
RiskOpportunity,
Strategic,
}
impl AnalysisDimension {
pub fn all() -> Vec<Self> {
vec![
Self::Economic,
Self::Technological,
Self::Social,
Self::Environmental,
Self::Political,
Self::Psychological,
Self::Ethical,
Self::Historical,
Self::Competitive,
Self::UserExperience,
Self::RiskOpportunity,
Self::Strategic,
]
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Economic => "Economic/Financial",
Self::Technological => "Technological/Innovation",
Self::Social => "Social/Cultural",
Self::Environmental => "Environmental/Sustainability",
Self::Political => "Political/Regulatory",
Self::Psychological => "Psychological/Behavioral",
Self::Ethical => "Ethical/Moral",
Self::Historical => "Historical/Evolutionary",
Self::Competitive => "Competitive/Market",
Self::UserExperience => "User Experience/Adoption",
Self::RiskOpportunity => "Risk/Opportunity",
Self::Strategic => "Long-term/Strategic",
}
}
pub fn guiding_questions(&self) -> Vec<&'static str> {
match self {
Self::Economic => vec![
"What are the financial implications?",
"How does this affect costs and revenues?",
"What economic forces are at play?",
],
Self::Technological => vec![
"What technologies enable or constrain this?",
"How might technology evolve to change this?",
"What innovation opportunities exist?",
],
Self::Social => vec![
"How does society perceive this?",
"What cultural factors influence adoption?",
"Who are the key stakeholders affected?",
],
Self::Environmental => vec![
"What is the environmental impact?",
"How sustainable is this approach?",
"What ecological factors are relevant?",
],
Self::Political => vec![
"What regulations apply or might apply?",
"How do political dynamics affect this?",
"What policy changes could impact this?",
],
Self::Psychological => vec![
"What cognitive biases might be at play?",
"How do people emotionally respond?",
"What behavioral patterns are relevant?",
],
Self::Ethical => vec![
"What are the moral implications?",
"Who might be harmed or helped?",
"What ethical principles apply?",
],
Self::Historical => vec![
"What historical precedents exist?",
"How has this evolved over time?",
"What can we learn from the past?",
],
Self::Competitive => vec![
"Who are the competitors?",
"What are the market dynamics?",
"How do switching costs affect this?",
],
Self::UserExperience => vec![
"How does this affect the user?",
"What friction points exist?",
"How can adoption be improved?",
],
Self::RiskOpportunity => vec![
"What are the key risks?",
"What opportunities might emerge?",
"How can risks be mitigated?",
],
Self::Strategic => vec![
"What is the long-term impact?",
"How does this fit into larger goals?",
"What strategic options exist?",
],
}
}
pub fn prompt_template(&self) -> &'static str {
match self {
Self::Economic => {
"Analyze the economic and financial aspects. Consider costs, benefits, \
market forces, pricing dynamics, and value creation potential."
}
Self::Technological => {
"Examine the technological dimensions. Consider enabling technologies, \
technical constraints, innovation potential, and technical debt."
}
Self::Social => {
"Explore the social and cultural factors. Consider stakeholder interests, \
social norms, cultural adoption barriers, and community impact."
}
Self::Environmental => {
"Assess environmental implications. Consider sustainability, ecological \
footprint, resource consumption, and environmental regulations."
}
Self::Political => {
"Analyze the political and regulatory landscape. Consider current \
regulations, potential policy changes, and political stakeholders."
}
Self::Psychological => {
"Examine psychological and behavioral factors. Consider cognitive biases, \
emotional responses, habit formation, and decision-making patterns."
}
Self::Ethical => {
"Evaluate ethical and moral dimensions. Consider fairness, transparency, \
potential harms, beneficiaries, and alignment with ethical principles."
}
Self::Historical => {
"Review historical context and precedents. Consider how similar situations \
evolved, lessons learned, and historical patterns that might repeat."
}
Self::Competitive => {
"Analyze competitive dynamics. Consider existing competitors, potential \
entrants, substitute solutions, and market positioning."
}
Self::UserExperience => {
"Assess user experience and adoption factors. Consider ease of use, \
learning curve, friction points, and paths to adoption."
}
Self::RiskOpportunity => {
"Evaluate risks and opportunities. Consider potential downsides, \
upside scenarios, mitigation strategies, and contingency plans."
}
Self::Strategic => {
"Examine long-term strategic implications. Consider competitive advantage, \
strategic positioning, future optionality, and path dependencies."
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Perspective {
pub id: String,
pub dimension: AnalysisDimension,
pub title: String,
pub content: String,
pub key_insight: String,
pub supporting_evidence: Vec<String>,
pub implications: Vec<String>,
pub confidence: f64,
pub novelty_score: f64,
pub depth_score: f64,
}
impl Perspective {
pub fn new(
id: impl Into<String>,
dimension: AnalysisDimension,
title: impl Into<String>,
content: impl Into<String>,
) -> Self {
Self {
id: id.into(),
dimension,
title: title.into(),
content: content.into(),
key_insight: String::new(),
supporting_evidence: Vec::new(),
implications: Vec::new(),
confidence: 0.70,
novelty_score: 0.5,
depth_score: 0.5,
}
}
pub fn with_key_insight(mut self, insight: impl Into<String>) -> Self {
self.key_insight = insight.into();
self
}
pub fn with_evidence(mut self, evidence: impl Into<String>) -> Self {
self.supporting_evidence.push(evidence.into());
self
}
pub fn with_implication(mut self, implication: impl Into<String>) -> Self {
self.implications.push(implication.into());
self
}
pub fn with_confidence(mut self, confidence: f64) -> Self {
self.confidence = confidence.clamp(0.0, 1.0);
self
}
pub fn with_novelty(mut self, novelty: f64) -> Self {
self.novelty_score = novelty.clamp(0.0, 1.0);
self
}
pub fn with_depth(mut self, depth: f64) -> Self {
self.depth_score = depth.clamp(0.0, 1.0);
self
}
pub fn quality_score(&self) -> f64 {
(self.confidence * 0.4 + self.novelty_score * 0.3 + self.depth_score * 0.3).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Theme {
pub id: String,
pub title: String,
pub description: String,
pub contributing_perspectives: Vec<String>,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SynthesizedInsight {
pub id: String,
pub content: String,
pub source_perspectives: Vec<String>,
pub actionability: f64,
pub confidence: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigaThinkResult {
pub query: String,
pub dimensions_explored: Vec<AnalysisDimension>,
pub perspectives: Vec<Perspective>,
pub themes: Vec<Theme>,
pub insights: Vec<SynthesizedInsight>,
pub confidence: f64,
pub cross_validated: bool,
pub metadata: GigaThinkMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GigaThinkMetadata {
pub version: String,
pub duration_ms: u64,
pub dimensions_count: usize,
pub perspectives_count: usize,
pub config: GigaThinkConfig,
}
pub trait AsyncThinkToolModule: ThinkToolModule {
fn execute_async<'a>(
&'a self,
context: &'a ThinkToolContext,
) -> Pin<Box<dyn Future<Output = Result<ThinkToolOutput>> + Send + 'a>>;
}
pub struct GigaThink {
module_config: ThinkToolModuleConfig,
config: GigaThinkConfig,
}
impl Default for GigaThink {
fn default() -> Self {
Self::new()
}
}
impl GigaThink {
pub fn new() -> Self {
Self::with_config(GigaThinkConfig::default())
}
pub fn with_config(config: GigaThinkConfig) -> Self {
Self {
module_config: ThinkToolModuleConfig {
name: "GigaThink".to_string(),
version: "2.1.0".to_string(),
description: "Expansive creative thinking with 10+ diverse perspectives"
.to_string(),
confidence_weight: 0.15,
},
config,
}
}
pub fn config(&self) -> &GigaThinkConfig {
&self.config
}
fn validate_query(&self, query: &str) -> Result<()> {
let length = query.len();
if length < self.config.min_query_length {
return Err(GigaThinkError::QueryTooShort {
length,
minimum: self.config.min_query_length,
}
.into());
}
if length > self.config.max_query_length {
return Err(GigaThinkError::QueryTooLong {
length,
maximum: self.config.max_query_length,
}
.into());
}
Ok(())
}
fn get_dimensions(&self) -> Vec<AnalysisDimension> {
if self.config.dimensions.is_empty() {
AnalysisDimension::all()
} else {
self.config.dimensions.clone()
}
}
fn generate_perspectives(
&self,
query: &str,
dimensions: &[AnalysisDimension],
) -> Vec<Perspective> {
dimensions
.par_iter()
.enumerate()
.map(|(idx, dim)| self.generate_perspective_for_dimension(query, *dim, idx))
.collect()
}
fn generate_perspective_for_dimension(
&self,
query: &str,
dimension: AnalysisDimension,
index: usize,
) -> Perspective {
let id = format!("perspective_{}", index + 1);
let title = format!("{} Analysis", dimension.display_name());
let content = self.generate_dimension_content(query, dimension);
let key_insight = self.extract_key_insight(query, dimension);
let novelty_score = self.calculate_novelty_score(query, dimension);
let depth_score = self.calculate_depth_score(query, dimension);
let confidence = self.calculate_perspective_confidence(novelty_score, depth_score);
let mut perspective = Perspective::new(id, dimension, title, content)
.with_key_insight(key_insight)
.with_confidence(confidence)
.with_novelty(novelty_score)
.with_depth(depth_score);
for evidence in self.generate_evidence(query, dimension) {
perspective = perspective.with_evidence(evidence);
}
for implication in self.generate_implications(query, dimension) {
perspective = perspective.with_implication(implication);
}
perspective
}
fn generate_dimension_content(&self, query: &str, dimension: AnalysisDimension) -> String {
format!(
"From the {} perspective, analyzing \"{}\":\n\n{}\n\nThis dimension reveals \
important considerations that may not be immediately apparent in other analyses.",
dimension.display_name(),
query,
dimension.prompt_template()
)
}
fn extract_key_insight(&self, _query: &str, dimension: AnalysisDimension) -> String {
format!(
"The {} lens reveals unique factors that warrant deeper exploration.",
dimension.display_name()
)
}
fn generate_evidence(&self, _query: &str, dimension: AnalysisDimension) -> Vec<String> {
dimension
.guiding_questions()
.iter()
.map(|q| format!("Addressed: {}", q))
.collect()
}
fn generate_implications(&self, _query: &str, dimension: AnalysisDimension) -> Vec<String> {
vec![format!(
"The {} dimension has significant implications for decision-making.",
dimension.display_name()
)]
}
fn calculate_novelty_score(&self, _query: &str, _dimension: AnalysisDimension) -> f64 {
0.75
}
fn calculate_depth_score(&self, _query: &str, _dimension: AnalysisDimension) -> f64 {
0.72
}
fn calculate_perspective_confidence(&self, novelty: f64, depth: f64) -> f64 {
(novelty * self.config.novelty_weight
+ depth * self.config.depth_weight
+ 0.80 * self.config.coherence_weight)
.clamp(0.0, 1.0)
}
fn identify_themes(&self, perspectives: &[Perspective]) -> Vec<Theme> {
let mut themes = Vec::new();
if perspectives.len() >= 3 {
themes.push(Theme {
id: "theme_1".to_string(),
title: "Cross-Dimensional Patterns".to_string(),
description: "Patterns that emerge across multiple analytical dimensions."
.to_string(),
contributing_perspectives: perspectives
.iter()
.take(4)
.map(|p| p.id.clone())
.collect(),
confidence: 0.78,
});
}
if perspectives.len() >= 6 {
themes.push(Theme {
id: "theme_2".to_string(),
title: "Stakeholder Impact".to_string(),
description: "How different stakeholders are affected across dimensions."
.to_string(),
contributing_perspectives: perspectives
.iter()
.filter(|p| {
matches!(
p.dimension,
AnalysisDimension::Social
| AnalysisDimension::UserExperience
| AnalysisDimension::Ethical
)
})
.map(|p| p.id.clone())
.collect(),
confidence: 0.82,
});
}
themes
}
fn synthesize_insights(
&self,
perspectives: &[Perspective],
themes: &[Theme],
) -> Vec<SynthesizedInsight> {
let mut insights = Vec::new();
let high_conf_perspectives: Vec<_> = perspectives
.iter()
.filter(|p| p.confidence > 0.75)
.collect();
if !high_conf_perspectives.is_empty() {
insights.push(SynthesizedInsight {
id: "insight_1".to_string(),
content: format!(
"High-confidence analysis from {} perspectives suggests actionable opportunities.",
high_conf_perspectives.len()
),
source_perspectives: high_conf_perspectives.iter().map(|p| p.id.clone()).collect(),
actionability: 0.80,
confidence: 0.85,
});
}
for (idx, theme) in themes.iter().enumerate() {
insights.push(SynthesizedInsight {
id: format!("insight_{}", idx + 2),
content: format!(
"Theme '{}' integrates insights from {} perspectives.",
theme.title,
theme.contributing_perspectives.len()
),
source_perspectives: theme.contributing_perspectives.clone(),
actionability: 0.70,
confidence: theme.confidence,
});
}
insights
}
fn cross_validate(&self, perspectives: &[Perspective]) -> Result<bool> {
if !self.config.enable_cross_validation {
return Ok(true);
}
if perspectives.len() < self.config.min_perspectives {
return Err(GigaThinkError::InsufficientPerspectives {
generated: perspectives.len(),
required: self.config.min_perspectives,
}
.into());
}
for perspective in perspectives {
if perspective.confidence < self.config.min_confidence {
return Err(GigaThinkError::LowConfidence {
confidence: perspective.confidence,
threshold: self.config.min_confidence,
}
.into());
}
}
let avg_confidence =
perspectives.iter().map(|p| p.confidence).sum::<f64>() / perspectives.len() as f64;
if avg_confidence < self.config.min_confidence {
return Err(GigaThinkError::CrossValidationFailed {
reason: format!(
"Average confidence {:.2} below threshold {:.2}",
avg_confidence, self.config.min_confidence
),
}
.into());
}
Ok(true)
}
fn calculate_overall_confidence(&self, perspectives: &[Perspective]) -> f64 {
if perspectives.is_empty() {
return 0.0;
}
let total_quality: f64 = perspectives.iter().map(|p| p.quality_score()).sum();
let avg_quality = total_quality / perspectives.len() as f64;
let unique_dimensions: std::collections::HashSet<_> =
perspectives.iter().map(|p| p.dimension).collect();
let diversity_factor = (unique_dimensions.len() as f64 / 12.0).min(1.0);
(avg_quality * 0.7 + diversity_factor * 0.3).clamp(0.0, 1.0)
}
fn build_result(
&self,
context: &ThinkToolContext,
duration_ms: u64,
) -> Result<GigaThinkResult> {
let query = &context.query;
let dimensions = self.get_dimensions();
let perspectives = self.generate_perspectives(query, &dimensions);
let cross_validated = self.cross_validate(&perspectives)?;
let themes = self.identify_themes(&perspectives);
let insights = self.synthesize_insights(&perspectives, &themes);
let confidence = self.calculate_overall_confidence(&perspectives);
Ok(GigaThinkResult {
query: query.clone(),
dimensions_explored: dimensions.clone(),
perspectives,
themes,
insights,
confidence,
cross_validated,
metadata: GigaThinkMetadata {
version: self.module_config.version.clone(),
duration_ms,
dimensions_count: dimensions.len(),
perspectives_count: self.config.min_perspectives,
config: self.config.clone(),
},
})
}
}
impl ThinkToolModule for GigaThink {
fn config(&self) -> &ThinkToolModuleConfig {
&self.module_config
}
fn execute(&self, context: &ThinkToolContext) -> Result<ThinkToolOutput> {
let start = std::time::Instant::now();
self.validate_query(&context.query)?;
let duration_ms = start.elapsed().as_millis() as u64;
let result = self.build_result(context, duration_ms)?;
let output = json!({
"dimensions": result.dimensions_explored.iter()
.map(|d| d.display_name())
.collect::<Vec<_>>(),
"perspectives": result.perspectives.iter().map(|p| json!({
"id": p.id,
"dimension": p.dimension.display_name(),
"title": p.title,
"key_insight": p.key_insight,
"confidence": p.confidence,
"quality_score": p.quality_score()
})).collect::<Vec<_>>(),
"themes": result.themes.iter().map(|t| json!({
"id": t.id,
"title": t.title,
"description": t.description,
"contributing_count": t.contributing_perspectives.len(),
"confidence": t.confidence
})).collect::<Vec<_>>(),
"insights": result.insights.iter().map(|i| json!({
"id": i.id,
"content": i.content,
"actionability": i.actionability,
"confidence": i.confidence
})).collect::<Vec<_>>(),
"confidence": result.confidence,
"cross_validated": result.cross_validated,
"metadata": {
"version": result.metadata.version,
"duration_ms": result.metadata.duration_ms,
"dimensions_count": result.metadata.dimensions_count,
"perspectives_count": result.metadata.perspectives_count
}
});
Ok(ThinkToolOutput {
module: self.module_config.name.clone(),
confidence: result.confidence,
output,
})
}
}
impl AsyncThinkToolModule for GigaThink {
fn execute_async<'a>(
&'a self,
context: &'a ThinkToolContext,
) -> Pin<Box<dyn Future<Output = Result<ThinkToolOutput>> + Send + 'a>> {
Box::pin(async move {
self.execute(context)
})
}
}
#[derive(Default)]
pub struct GigaThinkBuilder {
config: GigaThinkConfig,
}
impl GigaThinkBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn min_perspectives(mut self, count: usize) -> Self {
self.config.min_perspectives = count;
self
}
pub fn max_perspectives(mut self, count: usize) -> Self {
self.config.max_perspectives = count;
self
}
pub fn min_confidence(mut self, confidence: f64) -> Self {
self.config.min_confidence = confidence.clamp(0.0, 1.0);
self
}
pub fn cross_validation(mut self, enabled: bool) -> Self {
self.config.enable_cross_validation = enabled;
self
}
pub fn dimensions(mut self, dimensions: Vec<AnalysisDimension>) -> Self {
self.config.dimensions = dimensions;
self
}
pub fn novelty_weight(mut self, weight: f64) -> Self {
self.config.novelty_weight = weight.clamp(0.0, 1.0);
self
}
pub fn depth_weight(mut self, weight: f64) -> Self {
self.config.depth_weight = weight.clamp(0.0, 1.0);
self
}
pub fn coherence_weight(mut self, weight: f64) -> Self {
self.config.coherence_weight = weight.clamp(0.0, 1.0);
self
}
pub fn max_execution_time_ms(mut self, ms: u64) -> Self {
self.config.max_execution_time_ms = Some(ms);
self
}
pub fn build(self) -> GigaThink {
GigaThink::with_config(self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gigathink_creation() {
let module = GigaThink::new();
use crate::thinktool::ThinkToolModule;
assert_eq!(ThinkToolModule::config(&module).name, "GigaThink");
assert_eq!(ThinkToolModule::config(&module).version, "2.1.0");
}
#[test]
fn test_builder_pattern() {
let module = GigaThinkBuilder::new()
.min_perspectives(12)
.max_perspectives(20)
.min_confidence(0.80)
.cross_validation(false)
.build();
assert_eq!(module.config.min_perspectives, 12);
assert_eq!(module.config.max_perspectives, 20);
assert_eq!(module.config.min_confidence, 0.80);
assert!(!module.config.enable_cross_validation);
}
#[test]
fn test_all_dimensions() {
let dimensions = AnalysisDimension::all();
assert_eq!(dimensions.len(), 12);
}
#[test]
fn test_dimension_display_names() {
assert_eq!(
AnalysisDimension::Economic.display_name(),
"Economic/Financial"
);
assert_eq!(
AnalysisDimension::Technological.display_name(),
"Technological/Innovation"
);
}
#[test]
fn test_dimension_guiding_questions() {
let questions = AnalysisDimension::Economic.guiding_questions();
assert!(!questions.is_empty());
assert!(questions[0].contains("financial"));
}
#[test]
fn test_perspective_creation() {
let perspective = Perspective::new(
"p1",
AnalysisDimension::Economic,
"Economic Analysis",
"Content",
)
.with_key_insight("Key insight")
.with_evidence("Evidence 1")
.with_implication("Implication 1")
.with_confidence(0.85)
.with_novelty(0.75)
.with_depth(0.80);
assert_eq!(perspective.id, "p1");
assert_eq!(perspective.dimension, AnalysisDimension::Economic);
assert_eq!(perspective.confidence, 0.85);
assert!(!perspective.supporting_evidence.is_empty());
assert!(!perspective.implications.is_empty());
}
#[test]
fn test_perspective_quality_score() {
let perspective = Perspective::new("p1", AnalysisDimension::Economic, "Title", "Content")
.with_confidence(0.90)
.with_novelty(0.80)
.with_depth(0.70);
let quality = perspective.quality_score();
assert!((quality - 0.81).abs() < 0.01);
}
#[test]
fn test_query_validation_too_short() {
let module = GigaThink::new();
let result = module.validate_query("short");
assert!(result.is_err());
}
#[test]
fn test_query_validation_valid() {
let module = GigaThink::new();
let result = module.validate_query("This is a valid query for analysis");
assert!(result.is_ok());
}
#[test]
fn test_execute_generates_perspectives() {
let module = GigaThink::new();
let context = ThinkToolContext {
query: "What are the key factors for startup success?".to_string(),
previous_steps: vec![],
};
let result = module.execute(&context).unwrap();
assert_eq!(result.module, "GigaThink");
assert!(result.confidence > 0.0);
let perspectives = result
.output
.get("perspectives")
.unwrap()
.as_array()
.unwrap();
assert!(perspectives.len() >= 10);
}
#[test]
fn test_execute_includes_metadata() {
let module = GigaThink::new();
let context = ThinkToolContext {
query: "What are the implications of AI on employment?".to_string(),
previous_steps: vec![],
};
let result = module.execute(&context).unwrap();
let metadata = result.output.get("metadata").unwrap();
assert!(metadata.get("version").is_some());
assert!(metadata.get("duration_ms").is_some());
assert!(metadata.get("dimensions_count").is_some());
}
#[test]
fn test_cross_validation() {
let module = GigaThink::new();
let perspectives: Vec<Perspective> = AnalysisDimension::all()
.iter()
.enumerate()
.map(|(i, dim)| {
Perspective::new(
format!("p{}", i),
*dim,
format!("{} Analysis", dim.display_name()),
"Content",
)
.with_confidence(0.80)
})
.collect();
let result = module.cross_validate(&perspectives);
assert!(result.is_ok());
}
#[test]
fn test_cross_validation_fails_low_confidence() {
let module = GigaThink::new();
let perspectives: Vec<Perspective> = AnalysisDimension::all()
.iter()
.enumerate()
.map(|(i, dim)| {
Perspective::new(
format!("p{}", i),
*dim,
format!("{} Analysis", dim.display_name()),
"Content",
)
.with_confidence(0.50) })
.collect();
let result = module.cross_validate(&perspectives);
assert!(result.is_err());
}
#[test]
fn test_theme_identification() {
let module = GigaThink::new();
let perspectives: Vec<Perspective> = AnalysisDimension::all()
.iter()
.enumerate()
.map(|(i, dim)| {
Perspective::new(format!("p{}", i), *dim, "Title", "Content").with_confidence(0.80)
})
.collect();
let themes = module.identify_themes(&perspectives);
assert!(!themes.is_empty());
}
#[test]
fn test_insight_synthesis() {
let module = GigaThink::new();
let perspectives: Vec<Perspective> = vec![
Perspective::new("p1", AnalysisDimension::Economic, "Title", "Content")
.with_confidence(0.85),
Perspective::new("p2", AnalysisDimension::Social, "Title", "Content")
.with_confidence(0.80),
];
let themes = vec![Theme {
id: "t1".to_string(),
title: "Theme 1".to_string(),
description: "Description".to_string(),
contributing_perspectives: vec!["p1".to_string(), "p2".to_string()],
confidence: 0.75,
}];
let insights = module.synthesize_insights(&perspectives, &themes);
assert!(!insights.is_empty());
}
#[tokio::test]
async fn test_async_execution() {
let module = GigaThink::new();
let context = ThinkToolContext {
query: "What are the future trends in renewable energy?".to_string(),
previous_steps: vec![],
};
let result = module.execute_async(&context).await.unwrap();
assert_eq!(result.module, "GigaThink");
assert!(result.confidence > 0.0);
}
#[test]
fn test_error_types() {
let err = GigaThinkError::InsufficientPerspectives {
generated: 5,
required: 10,
};
assert!(err.to_string().contains("5"));
assert!(err.to_string().contains("10"));
let err = GigaThinkError::QueryTooShort {
length: 5,
minimum: 10,
};
assert!(err.to_string().contains("too short"));
let err = GigaThinkError::LowConfidence {
confidence: 0.50,
threshold: 0.70,
};
assert!(err.to_string().contains("0.50"));
}
#[test]
fn test_reasoning_mode_default() {
let mode = ReasoningMode::default();
assert_eq!(mode, ReasoningMode::Standard);
}
#[test]
fn test_reasoning_mode_system_prompts() {
assert!(ReasoningMode::Standard.system_prompt_modifier().is_empty());
assert!(ReasoningMode::ChainOfDraft
.system_prompt_modifier()
.contains("5 words"));
assert!(ReasoningMode::Adaptive
.system_prompt_modifier()
.contains("brief"));
}
#[test]
fn test_reasoning_mode_answer_marker() {
assert!(ReasoningMode::Standard.answer_marker().is_empty());
assert_eq!(ReasoningMode::ChainOfDraft.answer_marker(), "####");
assert_eq!(ReasoningMode::Adaptive.answer_marker(), "####");
}
#[test]
fn test_reasoning_mode_token_efficient() {
assert!(!ReasoningMode::Standard.is_token_efficient());
assert!(ReasoningMode::ChainOfDraft.is_token_efficient());
assert!(ReasoningMode::Adaptive.is_token_efficient());
}
#[test]
fn test_chain_of_draft_config_default() {
let config = ChainOfDraftConfig::default();
assert_eq!(config.words_per_step, 5);
assert!(!config.strict_word_budget);
assert_eq!(config.answer_marker, "####");
assert!(config.include_step_numbers);
assert!((config.adaptive_expansion_threshold - 0.7).abs() < 0.001);
}
#[test]
fn test_cod_utils_count_words() {
assert_eq!(ChainOfDraftUtils::count_words("hello world"), 2);
assert_eq!(ChainOfDraftUtils::count_words("one two three four five"), 5);
assert_eq!(ChainOfDraftUtils::count_words(""), 0);
assert_eq!(ChainOfDraftUtils::count_words(" spaced out "), 2);
}
#[test]
fn test_cod_utils_exceeds_word_budget() {
assert!(!ChainOfDraftUtils::exceeds_word_budget("one two three", 5));
assert!(!ChainOfDraftUtils::exceeds_word_budget(
"one two three four five",
5
));
assert!(ChainOfDraftUtils::exceeds_word_budget(
"one two three four five six",
5
));
}
#[test]
fn test_cod_utils_extract_answer() {
let output = "Step 1: analyze problem\nStep 2: identify key factors\n#### The answer is 42";
let answer = ChainOfDraftUtils::extract_answer(output, "####");
assert_eq!(answer, Some("The answer is 42".to_string()));
let no_answer = ChainOfDraftUtils::extract_answer("no marker here", "####");
assert!(no_answer.is_none());
let empty_marker = ChainOfDraftUtils::extract_answer(output, "");
assert!(empty_marker.is_none());
}
#[test]
fn test_cod_utils_parse_draft_steps() {
let output = "1. Identify key factors\n2. Analyze relationships\n3. Draw conclusion\n#### Final answer";
let steps = ChainOfDraftUtils::parse_draft_steps(output, "####");
assert_eq!(steps.len(), 3);
assert!(steps[0].contains("Identify"));
assert!(steps[1].contains("Analyze"));
assert!(steps[2].contains("Draw"));
}
#[test]
fn test_cod_utils_token_efficiency() {
let efficiency = ChainOfDraftUtils::estimate_token_efficiency(38, 500);
assert!((efficiency - 0.076).abs() < 0.001);
let edge = ChainOfDraftUtils::estimate_token_efficiency(10, 0);
assert!((edge - 1.0).abs() < 0.001);
}
#[test]
fn test_cod_utils_generate_prompt() {
let prompt = ChainOfDraftUtils::generate_cod_prompt("What is 2+2?", 5);
assert!(prompt.contains("What is 2+2?"));
assert!(prompt.contains("5 words"));
assert!(prompt.contains("####"));
}
#[test]
fn test_cod_utils_dimension_prompt() {
let prompt = ChainOfDraftUtils::generate_dimension_cod_prompt(
"Analyze startup success",
"Economic/Financial",
"What are the financial implications?",
5,
);
assert!(prompt.contains("Economic/Financial"));
assert!(prompt.contains("financial implications"));
assert!(prompt.contains("5 words"));
}
#[test]
fn test_chain_of_draft_metrics() {
let output = "1. Check inputs\n2. Validate data\n3. Compute result\n#### 42";
let metrics = ChainOfDraftMetrics::from_draft(output, "####", 5);
assert_eq!(metrics.step_count, 3);
assert!(metrics.total_reasoning_words > 0);
assert!(metrics.avg_words_per_step > 0.0);
assert!(metrics.token_efficiency_ratio < 1.0);
assert!(!metrics.adaptive_expansion_used);
}
#[test]
fn test_gigathink_config_chain_of_draft() {
let config = GigaThinkConfig::chain_of_draft();
assert_eq!(config.reasoning_mode, ReasoningMode::ChainOfDraft);
assert_eq!(config.min_perspectives, 5);
assert_eq!(config.max_perspectives, 8);
assert!((config.min_confidence - 0.65).abs() < 0.001);
}
#[test]
fn test_gigathink_config_adaptive() {
let config = GigaThinkConfig::adaptive();
assert_eq!(config.reasoning_mode, ReasoningMode::Adaptive);
assert_eq!(config.min_perspectives, 7);
assert_eq!(config.max_perspectives, 12);
}
#[test]
fn test_gigathink_config_builder_methods() {
let config = GigaThinkConfig::default()
.with_reasoning_mode(ReasoningMode::ChainOfDraft)
.with_words_per_step(7);
assert_eq!(config.reasoning_mode, ReasoningMode::ChainOfDraft);
assert_eq!(config.cod_config.words_per_step, 7);
}
#[test]
fn test_cod_metrics_steps_over_budget() {
let output =
"1. Check\n2. This step has many more words than allowed\n3. Done\n#### Result";
let metrics = ChainOfDraftMetrics::from_draft(output, "####", 3);
assert!(metrics.steps_over_budget >= 1);
}
}