feat: ContentGuardianAgent, onboarding UX, Team Activity action wiring, docs, agent help modal
ContentGuardianAgent consolidation:
- Merge 3 duplicate classes into single source in specialized/content_guardian.py
- Watchdog audit_committee() with heuristic scoring, coverage gaps, overlaps, alerts
- Remove misleading rejection_rate() helper; use acceptance_rate directly
- Integrate audit + alerts + trend signals into today_workflow_service.py
Team Activity page:
- QualityAuditPanel: health ring, per-agent critiques, coverage gaps, overlaps
- TrendSignalsPanel: opportunity cards with urgency/impact/coverage bars
- AlertBanner: persistent dismiss via POST /alerts/{id}/mark-read
- AgentHelpModal: dialog showing all 8 agents with descriptions, tools, schedule
- QualityAuditPanel action buttons: Fill gap -> /content-planning, Resolve overlap, View CTA on alerts/issues
- TrendSignalsPanel action buttons: Create content from this trend -> /blog-writer with trend context state
Onboarding system:
- Step 4 validation: no auto-pass via basic_ready; requires persona data or explicit progression
- Step 5 validation: logs warning on auto-pass without integration data
- OnboardingCompletionService: single DB session, transactional task creation, upsert pattern
- Business-without-website: nullable website_url on SIFIndexingTask and MarketTrendsTask
- DeepCompetitorAnalysisExecutor: 5-min timeout, 10-competitor cap, asyncio.wait_for
- Persona generation: async with 30s timeout, falls back to scheduler
- OnboardingProgressService.reset_onboarding(): resets session + pauses all DB tasks
- OnboardingControlService.reset_onboarding(): also cancels APScheduler jobs
- FinalStep TaskSchedulingPanel: shows scheduled/failed tasks after completion, 8s auto-redirect
- onboarding_completed agent activity event logged to feed
Documentation:
- docs-site/features/onboarding/: overview, steps, scheduler-tasks, technical-reference (4 pages)
- docs-site/mkdocs.yml: added Onboarding System nav section
- docs-site/features/sif-agents/: overview, agent-directory, committee-system, content-guardian (4 pages)
- docs-site/features/team-activity/: overview, quality-audit, trend-signals, alert-system (4 pages)
- docs-site/features/todays-workflow/: updated overview, technical-architecture, workflow-guide, api-reference
This commit is contained in:
@@ -375,9 +375,13 @@ def llm_text_gen(
|
||||
system_prompt=system_instructions
|
||||
)
|
||||
elif gpt_provider == "wavespeed":
|
||||
llm_start = time.time()
|
||||
t0 = time.time()
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: Starting provider init for user {user_id}")
|
||||
if json_struct:
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: Importing wavespeed_provider module (lazy import) for user {user_id}")
|
||||
from services.llm_providers.wavespeed_provider import wavespeed_structured_json_response
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: Import done, making API call for user {user_id}, import_took={(time.time()-t0)*1000:.0f}ms")
|
||||
t1 = time.time()
|
||||
response_text = wavespeed_structured_json_response(
|
||||
prompt=prompt,
|
||||
schema=json_struct,
|
||||
@@ -387,7 +391,10 @@ def llm_text_gen(
|
||||
system_prompt=system_instructions
|
||||
)
|
||||
else:
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: Importing wavespeed_provider module (lazy import) for user {user_id}")
|
||||
from services.llm_providers.wavespeed_provider import wavespeed_text_response
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: Import done, making API call for user {user_id}, import_took={(time.time()-t0)*1000:.0f}ms")
|
||||
t1 = time.time()
|
||||
response_text = wavespeed_text_response(
|
||||
prompt=prompt,
|
||||
model=model or "openai/gpt-oss-120b",
|
||||
@@ -396,8 +403,9 @@ def llm_text_gen(
|
||||
top_p=top_p,
|
||||
system_prompt=system_instructions
|
||||
)
|
||||
llm_ms = (time.time() - llm_start) * 1000
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] LLM API call took {llm_ms:.0f}ms for user {user_id} (wavespeed)")
|
||||
api_took_ms = (time.time() - t1) * 1000
|
||||
total_ms = (time.time() - t0) * 1000
|
||||
logger.warning(f"[llm_text_gen][{flow_tag}] wavespeed: user={user_id} import_took={(t1-t0)*1000:.0f}ms api_took={api_took_ms:.0f}ms total={total_ms:.0f}ms")
|
||||
else:
|
||||
logger.error(f"[llm_text_gen] Unknown provider: {gpt_provider}")
|
||||
raise RuntimeError(f"Unknown LLM provider: {gpt_provider}. Supported providers: google, huggingface, wavespeed")
|
||||
|
||||
@@ -38,6 +38,7 @@ Last Updated: March 2026
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time as _time
|
||||
from pathlib import Path
|
||||
import json
|
||||
import re
|
||||
@@ -46,15 +47,16 @@ from typing import Optional, Dict, Any, List
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Fix the environment loading path - load from backend directory
|
||||
_mod_start = _time.time()
|
||||
current_dir = Path(__file__).parent.parent # services directory
|
||||
backend_dir = current_dir.parent # backend directory
|
||||
env_path = backend_dir / '.env'
|
||||
|
||||
if env_path.exists():
|
||||
load_dotenv(env_path)
|
||||
print(f"Loaded .env from: {env_path}")
|
||||
_dotenv_ms = (_time.time() - _mod_start) * 1000
|
||||
print(f"Loaded .env from: {env_path} (took {_dotenv_ms:.0f}ms)")
|
||||
else:
|
||||
# Fallback to current directory
|
||||
load_dotenv()
|
||||
print(f"No .env found at {env_path}, using current directory")
|
||||
|
||||
@@ -64,6 +66,7 @@ from utils.logger_utils import get_service_logger
|
||||
# Use service-specific logger to avoid conflicts
|
||||
logger = get_service_logger("wavespeed_provider")
|
||||
|
||||
_import_start = _time.time()
|
||||
from tenacity import (
|
||||
retry,
|
||||
retry_if_exception,
|
||||
@@ -80,6 +83,8 @@ except ImportError:
|
||||
NotFoundError = Exception
|
||||
logger.warn("OpenAI library not available. Install with: pip install openai")
|
||||
|
||||
logger.warning(f"[wavespeed_provider] Module import completed in {(_time.time()-_import_start)*1000:.0f}ms (openai_available={OPENAI_AVAILABLE})")
|
||||
|
||||
# Default WaveSpeed models for fallback
|
||||
WAVESPEED_FALLBACK_MODELS = [
|
||||
"openai/gpt-oss-120b",
|
||||
@@ -276,12 +281,13 @@ def wavespeed_text_response(
|
||||
if not api_key:
|
||||
raise Exception("WAVESPEED_API_KEY not found in environment variables")
|
||||
|
||||
_t0 = _time.time()
|
||||
# Initialize WaveSpeed client
|
||||
client = OpenAI(
|
||||
base_url="https://llm.wavespeed.ai/v1",
|
||||
api_key=api_key,
|
||||
)
|
||||
logger.info("✅ WaveSpeed client initialized for text response")
|
||||
logger.warning(f"[wavespeed_text_response] OpenAI client init took {(_time.time()-_t0)*1000:.0f}ms")
|
||||
|
||||
# Prepare input for the API
|
||||
messages = []
|
||||
@@ -311,6 +317,7 @@ def wavespeed_text_response(
|
||||
|
||||
logger.info("🚀 Making WaveSpeed API call (chat completion)...")
|
||||
|
||||
_api_t0 = _time.time()
|
||||
# Call exactly the requested model; no retries, no fallbacks, no variants
|
||||
response = client.chat.completions.create(
|
||||
model=model,
|
||||
@@ -319,6 +326,7 @@ def wavespeed_text_response(
|
||||
top_p=top_p,
|
||||
max_tokens=max_tokens
|
||||
)
|
||||
logger.warning(f"[wavespeed_text_response] API call took {(_time.time()-_api_t0)*1000:.0f}ms")
|
||||
|
||||
# Extract text from response
|
||||
generated_text = response.choices[0].message.content
|
||||
@@ -422,13 +430,15 @@ def wavespeed_structured_json_response(
|
||||
|
||||
if not api_key:
|
||||
raise Exception("WAVESPEED_API_KEY not found in environment variables")
|
||||
|
||||
|
||||
_fn_start = _time.time()
|
||||
# Initialize OpenAI client with WaveSpeed base URL
|
||||
client = OpenAI(
|
||||
base_url="https://llm.wavespeed.ai/v1",
|
||||
api_key=api_key,
|
||||
)
|
||||
logger.info("✅ WaveSpeed client initialized for structured JSON response")
|
||||
_client_init_ms = (_time.time() - _fn_start) * 1000
|
||||
logger.warning(f"[wavespeed_structured_json_response] OpenAI client init took {_client_init_ms:.0f}ms")
|
||||
|
||||
# Prepare input for the API
|
||||
messages = []
|
||||
@@ -463,11 +473,13 @@ def wavespeed_structured_json_response(
|
||||
json_schema_str = json.dumps(schema, indent=2)
|
||||
messages[-1]["content"] += f"\n\nJSON Schema:\n{json_schema_str}"
|
||||
|
||||
_api_start = _time.time()
|
||||
try:
|
||||
response = None
|
||||
last_error = None
|
||||
for candidate_model in _fallback_model_sequence(model, fallback_models):
|
||||
try:
|
||||
logger.info(f"[wavespeed_structured_json_response] Calling model={candidate_model}...")
|
||||
response = client.chat.completions.create(
|
||||
model=candidate_model,
|
||||
messages=messages,
|
||||
@@ -475,8 +487,10 @@ def wavespeed_structured_json_response(
|
||||
max_tokens=max_tokens,
|
||||
response_format={"type": "json_object"} # Try to enforce JSON mode if supported
|
||||
)
|
||||
_api_ms = (_time.time() - _api_start) * 1000
|
||||
if candidate_model != model:
|
||||
logger.warning("WaveSpeed structured generation switched to fallback model: {}", candidate_model)
|
||||
logger.warning(f"[wavespeed_structured_json_response] First API call completed in {_api_ms:.0f}ms (model={candidate_model})")
|
||||
break
|
||||
except NotFoundError as nf_err:
|
||||
last_error = nf_err
|
||||
|
||||
Reference in New Issue
Block a user