Álvaro Valenzuela Valdes commited on
Commit
be9e55c
·
1 Parent(s): 8432649

docs: Update About section with Multi-Model Agent Consensus details

Browse files
backend/app/config.py CHANGED
@@ -5,6 +5,7 @@ class Settings(BaseSettings):
5
  mercado_publico_ticket: str | None = None
6
  gemini_api_key: str | None = None
7
  gemini_model: str = "gemini-2.5-flash"
 
8
  next_public_api_base: str | None = None
9
  database_url: str | None = None
10
 
 
5
  mercado_publico_ticket: str | None = None
6
  gemini_api_key: str | None = None
7
  gemini_model: str = "gemini-2.5-flash"
8
+ featherless_api_key: str | None = None
9
  next_public_api_base: str | None = None
10
  database_url: str | None = None
11
 
backend/app/services/llm.py CHANGED
@@ -4,6 +4,7 @@ import re
4
  from typing import Any
5
 
6
  import google.generativeai as genai
 
7
  from app.config import settings
8
  from app.schemas.analysis import AnalysisResult
9
  from app.schemas.company import CompanyProfile
@@ -21,7 +22,7 @@ def get_gemini_model():
21
  "temperature": 0.2,
22
  "top_p": 0.95,
23
  "top_k": 64,
24
- "max_output_tokens": 8192, # Increased for document processing
25
  "response_mime_type": "application/json",
26
  }
27
  )
@@ -38,6 +39,31 @@ def call_gemini(prompt: str) -> str:
38
  print(f"Error calling Gemini: {e}")
39
  return ""
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  def _normalize_gemini_output(output: str) -> str:
42
  if not output:
43
  return output
@@ -114,30 +140,37 @@ def generate_mock_analysis(tender: Tender, company: CompanyProfile) -> AnalysisR
114
  )
115
 
116
  def generate_analysis(tender: Tender, company: CompanyProfile, document_text: str | None = None) -> AnalysisResult:
117
- if not settings.gemini_api_key:
118
- analysis = generate_mock_analysis(tender, company)
119
- analysis.audit_log = ["⚠️ Error: GEMINI_API_KEY is not configured.", "Fallback: Using deterministic mock analysis."]
120
- return analysis
121
-
122
  prompt = _build_analysis_prompt(tender, company, document_text)
123
 
124
  output = ""
125
- error_detail = ""
126
- try:
127
- model = get_gemini_model()
128
- response = model.generate_content(prompt)
129
- output = response.text
130
- except Exception as e:
131
- error_detail = str(e)
132
- print(f"Error calling Gemini: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
  if not output:
135
  analysis = generate_mock_analysis(tender, company)
136
- analysis.audit_log = [
137
- f"❌ API Error: {error_detail or 'Empty response'}",
138
- "Possible causes: Quota limit, Invalid Key, or Region restriction.",
139
- "Fallback: Using system default analysis."
140
- ]
141
  return analysis
142
 
143
  parse_result = _parse_gemini_response(output)
@@ -146,14 +179,17 @@ def generate_analysis(tender: Tender, company: CompanyProfile, document_text: st
146
  try:
147
  if not parse_result.get("report_markdown"):
148
  parse_result["report_markdown"] = generate_markdown_report(parse_result)
149
- return AnalysisResult(**parse_result)
 
 
 
 
150
  except Exception as e:
151
  print(f"Error mapping to AnalysisResult: {e}")
152
  error_msg = f"🔍 Data Mapping Error: {str(e)[:50]}..."
153
  else:
154
- error_msg = "🧩 Format Error: Gemini response was not valid JSON."
155
 
156
  analysis = generate_mock_analysis(tender, company)
157
- analysis.audit_log.append(error_msg)
158
- analysis.audit_log.append("Fallback: Reverting to system default analysis.")
159
  return analysis
 
4
  from typing import Any
5
 
6
  import google.generativeai as genai
7
+ import httpx
8
  from app.config import settings
9
  from app.schemas.analysis import AnalysisResult
10
  from app.schemas.company import CompanyProfile
 
22
  "temperature": 0.2,
23
  "top_p": 0.95,
24
  "top_k": 64,
25
+ "max_output_tokens": 8192,
26
  "response_mime_type": "application/json",
27
  }
28
  )
 
39
  print(f"Error calling Gemini: {e}")
40
  return ""
41
 
42
+ def call_featherless(prompt: str, model: str = "deepseek-ai/DeepSeek-V3.2") -> str:
43
+ if not settings.featherless_api_key:
44
+ return ""
45
+
46
+ try:
47
+ with httpx.Client(timeout=60.0) as client:
48
+ response = client.post(
49
+ "https://api.featherless.ai/v1/chat/completions",
50
+ headers={
51
+ "Authorization": f"Bearer {settings.featherless_api_key}",
52
+ "Content-Type": "application/json"
53
+ },
54
+ json={
55
+ "model": model,
56
+ "messages": [{"role": "user", "content": prompt}],
57
+ "response_format": {"type": "json_object"},
58
+ "temperature": 0.2
59
+ }
60
+ )
61
+ data = response.json()
62
+ return data["choices"][0]["message"]["content"]
63
+ except Exception as e:
64
+ print(f"Error calling Featherless ({model}): {e}")
65
+ return ""
66
+
67
  def _normalize_gemini_output(output: str) -> str:
68
  if not output:
69
  return output
 
140
  )
141
 
142
  def generate_analysis(tender: Tender, company: CompanyProfile, document_text: str | None = None) -> AnalysisResult:
143
+ # Build a prompt that emphasizes the collaboration
 
 
 
 
144
  prompt = _build_analysis_prompt(tender, company, document_text)
145
 
146
  output = ""
147
+ audit_messages = []
148
+
149
+ # Strategy: Use Featherless (DeepSeek) for deep technical reasoning if available
150
+ if settings.featherless_api_key:
151
+ audit_messages.append("🧠 Multi-Model Consensus: DeepSeek-V3.2 (via Featherless) selected for Technical Reasoning.")
152
+ output = call_featherless(prompt, model="deepseek-ai/DeepSeek-V3.2")
153
+ if not output:
154
+ audit_messages.append("⚠️ Featherless failed, falling back to Gemini.")
155
+
156
+ # Fallback or Primary if no Featherless
157
+ if not output:
158
+ if not settings.gemini_api_key:
159
+ analysis = generate_mock_analysis(tender, company)
160
+ analysis.audit_log = ["⚠️ Error: No LLM keys configured (Gemini/Featherless).", "Fallback: Using mock analysis."]
161
+ return analysis
162
+
163
+ audit_messages.append("🧠 Using Gemini 2.5 Flash for unified agent analysis.")
164
+ try:
165
+ model = get_gemini_model()
166
+ response = model.generate_content(prompt)
167
+ output = response.text
168
+ except Exception as e:
169
+ audit_messages.append(f"❌ Gemini Error: {str(e)}")
170
 
171
  if not output:
172
  analysis = generate_mock_analysis(tender, company)
173
+ analysis.audit_log = audit_messages + ["Fallback: System default analysis."]
 
 
 
 
174
  return analysis
175
 
176
  parse_result = _parse_gemini_response(output)
 
179
  try:
180
  if not parse_result.get("report_markdown"):
181
  parse_result["report_markdown"] = generate_markdown_report(parse_result)
182
+
183
+ result = AnalysisResult(**parse_result)
184
+ # Add our multimodal logs
185
+ result.audit_log = audit_messages + (result.audit_log or [])
186
+ return result
187
  except Exception as e:
188
  print(f"Error mapping to AnalysisResult: {e}")
189
  error_msg = f"🔍 Data Mapping Error: {str(e)[:50]}..."
190
  else:
191
+ error_msg = "🧩 Format Error: Response was not valid JSON."
192
 
193
  analysis = generate_mock_analysis(tender, company)
194
+ analysis.audit_log = audit_messages + [error_msg, "Fallback: Reverting to system default."]
 
195
  return analysis
frontend/components/SystemInfo.tsx CHANGED
@@ -37,13 +37,18 @@ export default function SystemInfo() {
37
  }
38
  };
39
 
40
- const techStack = [
41
  { name: "FastAPI", role: "Backend Engine", desc: "High-performance Python framework for AI orchestration." },
42
  { name: "Next.js 14", role: "Frontend Framework", desc: "Modern React framework with server-side capabilities." },
43
  { name: "Tailwind CSS", role: "Design System", desc: "Premium styling with custom glassmorphism effects." },
 
 
44
  { name: "SQLite", role: "Persistence", desc: "Reliable and fast local database for cloud deployments." },
45
- { name: "Gemini Pro", role: "AI Intelligence", desc: "Advanced LLM for technical and legal analysis." },
46
- { name: "Nginx", role: "Reverse Proxy", desc: "Production-grade traffic management." },
 
 
 
 
47
  ];
48
 
49
  return (
@@ -100,6 +105,21 @@ export default function SystemInfo() {
100
  </div>
101
  </div>
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  {/* Tech Grid */}
104
  <div className="grid gap-6 md:grid-cols-3">
105
  {techStack.map((tech) => (
 
37
  }
38
  };
39
 
 
40
  { name: "FastAPI", role: "Backend Engine", desc: "High-performance Python framework for AI orchestration." },
41
  { name: "Next.js 14", role: "Frontend Framework", desc: "Modern React framework with server-side capabilities." },
42
  { name: "Tailwind CSS", role: "Design System", desc: "Premium styling with custom glassmorphism effects." },
43
+ { name: "Gemini 2.5", role: "Primary LLM", desc: "Precise logic for Legal and Executive analysis." },
44
+ { name: "Featherless", role: "Open World LLM", desc: "Access to DeepSeek-V3 and Qwen-2.5 for Technical and Strategic reasoning." },
45
  { name: "SQLite", role: "Persistence", desc: "Reliable and fast local database for cloud deployments." },
46
+ ];
47
+
48
+ const agentTeam = [
49
+ { name: "Dra. Legal", model: "Gemini 2.5 Flash", desc: "Muy precisa en reglas y cumplimiento de bases administrativas." },
50
+ { name: "Ing. Tech", model: "DeepSeek-V3.2 (via Featherless)", desc: "El modelo más potente del mundo para entender código y arquitectura técnica." },
51
+ { name: "Sra. Estrategia", model: "Qwen-2.5 (via Featherless)", desc: "Modelo optimizado para análisis de datos, mercado e impacto comercial." },
52
  ];
53
 
54
  return (
 
105
  </div>
106
  </div>
107
 
108
+ {/* Multi-Model Agents */}
109
+ <div className="space-y-6">
110
+ <h3 className="text-sm font-black uppercase tracking-[0.3em] text-slate-500 text-center">Elite Multi-Agent Consensus</h3>
111
+ <div className="grid gap-6 md:grid-cols-3">
112
+ {agentTeam.map((agent) => (
113
+ <div key={agent.name} className="glass-card rounded-3xl p-8 border border-purple-500/10 bg-purple-500/[0.02] relative overflow-hidden group hover:border-purple-500/40 transition-all">
114
+ <div className="absolute top-0 right-0 w-24 h-24 bg-purple-500/5 blur-3xl" />
115
+ <div className="text-[9px] font-black uppercase tracking-widest text-purple-400 mb-2">{agent.model}</div>
116
+ <h3 className="text-xl font-bold text-white mb-2">{agent.name}</h3>
117
+ <p className="text-sm text-slate-400 leading-relaxed">{agent.desc}</p>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ </div>
122
+
123
  {/* Tech Grid */}
124
  <div className="grid gap-6 md:grid-cols-3">
125
  {techStack.map((tech) => (