Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ OLLAMA_API_BASE=http://localhost:11434

# --- 2.4 External web search (Tavily) ---------------------
TAVILY_API_KEY=
# Optional: Olostep web search (alternative to Tavily)
OLOSTEP_API_KEY=
# Options: tavily, olostep
WEB_SEARCH_PROVIDER=tavily

# --- 2.5 Chat history DB (blank = reuse MONGODB_URI) ------
BEEVER_CHAT_HISTORY_DB=
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ Optional (skip unless you know you need them):
| Key | What it enables |
|---|---|
| `TAVILY_API_KEY` | External web search when QA retrieval confidence is low — [tavily.com](https://tavily.com/) |
| `OLOSTEP_API_KEY` | Olostep web search (alternative to Tavily). Set `WEB_SEARCH_PROVIDER=olostep` in your `.env` — [olostep.com/dashboard](https://www.olostep.com/dashboard) |
| Slack / Discord / Teams bot tokens | **Configured via the web UI after setup**, not `.env` — the bot stores platform credentials encrypted in MongoDB |

> **Tip:** Keep the two required keys handy before you start. Option 1 prompts for them interactively; Options 2 and 3 need them pasted into `.env`.
Expand Down
15 changes: 11 additions & 4 deletions atlas
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# Step 1/5 Embedding model (pick provider, then its key)
# Step 2/5 Agent LLM provider (pick provider; assign per-agent later in AI Setup)
# Step 3/5 Graph backend (neo4j or none)
# Step 4/5 Optional integrations (Tavily, MCP server)
# Step 4/5 Optional integrations (Tavily/Olostep, MCP server)
# Step 5/5 Auth tokens (keep dev defaults or rotate)
#
# For CI/Docker: pass BEEVER_LLM_API_KEY (single-provider shortcut) or
Expand Down Expand Up @@ -790,17 +790,24 @@ fi
# ---------------------------------------------------------------------
if [ "$NON_INTERACTIVE" != "true" ]; then
step "4/5" "Optional integrations"
hint "External web search (Tavily) and MCP for Claude Code / Cursor."
hint "External web search (Tavily or Olostep) and MCP for Claude Code / Cursor."

opt_parts=()
if confirm "N" "Configure optional integrations now?"; then
# Tavily
prompt_external_key "TAVILY_API_KEY" "Tavily (external web search in QA)" "https://tavily.com/"
# Olostep
prompt_external_key "OLOSTEP_API_KEY" "Olostep (external web search in QA)" "https://www.olostep.com/dashboard"
if grep -qE '^TAVILY_API_KEY=.+' .env 2>/dev/null; then
opt_parts+=("Tavily ✓")
else
opt_parts+=("Tavily skipped")
fi
if grep -qE '^OLOSTEP_API_KEY=.+' .env 2>/dev/null; then
opt_parts+=("Olostep ✓")
else
opt_parts+=("Olostep skipped")
fi

# Ollama is now a first-class agent-provider choice in Step 2 — no
# separate prompt here. (If you picked Ollama in Step 2 it's already
Expand All @@ -827,7 +834,7 @@ if [ "$NON_INTERACTIVE" != "true" ]; then
_atlas_summary+=("${C_GREEN}✓${C_RESET} Optional ${opt_parts[*]}")
else
ok "skipped optional integrations"
_atlas_summary+=("${C_DIM}—${C_RESET} Optional skipped (Tavily / MCP)")
_atlas_summary+=("${C_DIM}—${C_RESET} Optional skipped (Tavily / Olostep / MCP)")
fi
fi

Expand Down Expand Up @@ -874,7 +881,7 @@ fi
# "Finalizing secrets" so auto-gen logic sees the correct filled state.
# ---------------------------------------------------------------------
if [ "$NON_INTERACTIVE" = "true" ]; then
for _preseed_key in GOOGLE_API_KEY JINA_API_KEY TAVILY_API_KEY \
for _preseed_key in GOOGLE_API_KEY JINA_API_KEY TAVILY_API_KEY OLOSTEP_API_KEY WEB_SEARCH_PROVIDER \
EMBEDDING_PROVIDER EMBEDDING_MODEL EMBEDDING_DIMENSIONS \
EMBEDDING_API_KEY OPENAI_API_KEY COHERE_API_KEY \
VOYAGE_API_KEY MISTRAL_API_KEY \
Expand Down
2 changes: 1 addition & 1 deletion src/beever_atlas/agents/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
{
"name": "search_external_knowledge",
"category": "external",
"description": "Search external web knowledge via the Tavily API.",
"description": "Search external web knowledge via the configured provider (Tavily or Olostep).",
},
# Orchestration tools — available in deep mode only. Surfaced here so the
# Tools panel can disable them per request via AskRequest.disabled_tools.
Expand Down
68 changes: 65 additions & 3 deletions src/beever_atlas/agents/tools/external_tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""External knowledge tool: Tavily web search."""
"""External knowledge tool: web search."""

from __future__ import annotations

Expand All @@ -14,10 +14,10 @@

@cite_tool_output(kind="web_result")
async def search_external_knowledge(query: str, mode: str = "general") -> dict:
"""Search external web knowledge via Tavily API.
"""Search external web knowledge via Tavily or Olostep.

Cost: ~$0.01. Target latency: ~1s.
Requires TAVILY_API_KEY environment variable.
Requires TAVILY_API_KEY or OLOSTEP_API_KEY environment variable.

Args:
query: Search query.
Expand All @@ -33,6 +33,32 @@ async def search_external_knowledge(query: str, mode: str = "general") -> dict:
from beever_atlas.infra.config import get_settings

settings = get_settings()
provider = settings.web_search_provider
logger.info("web_search.provider=%s mode=%s", provider, mode)
if provider == "olostep":
api_key = settings.olostep_api_key
if not api_key:
return {
"error": "olostep_unavailable",
"message": "OLOSTEP_API_KEY is not configured. External search unavailable.",
"results": [],
"source": "external",
}

results = await asyncio.to_thread(
search_with_olostep,
query,
api_key,
5,
)

return {
"answer": "",
"results": results,
"source": "external_olostep",
"mode": mode,
}

api_key = settings.tavily_api_key
if not api_key:
return {
Expand Down Expand Up @@ -91,3 +117,39 @@ async def search_external_knowledge(query: str, mode: str = "general") -> dict:
"results": [],
"source": "external",
}


def search_with_olostep(query: str, api_key: str, max_results: int = 5) -> list[dict]:
"""Search the web using Olostep /searches endpoint.

Returns a list of result dicts with 'title', 'url', 'content', 'text',
and 'score' keys to match the shape expected by callers.
"""
import httpx

response = httpx.post(
"https://api.olostep.com/v1/searches",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={"query": query},
timeout=30,
)
response.raise_for_status()
data = response.json()
links = data.get("result", {}).get("links", [])
if not links:
logger.warning("olostep response missing links: %s", list(data.keys()))
links = links[:max_results]

return [
{
"title": link.get("title", ""),
"url": link.get("url", ""),
"content": link.get("description", "")[:500],
"text": link.get("description", "")[:500],
"score": 0.0,
}
for link in links
]
2 changes: 2 additions & 0 deletions src/beever_atlas/infra/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class Settings(BaseSettings):
# External services
jina_api_key: str = Field(default="")
tavily_api_key: str = Field(default="")
olostep_api_key: str = Field(default="")
web_search_provider: Literal["tavily", "olostep"] = Field(default="tavily")

# LLM model tiers (ADK pipeline)
llm_fast_model: str = Field(default="gemini-2.5-flash")
Expand Down
2 changes: 2 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_all_api_key_fields_exist(self):
assert hasattr(settings, "google_api_key")
assert hasattr(settings, "jina_api_key")
assert hasattr(settings, "tavily_api_key")
assert hasattr(settings, "olostep_api_key")
assert hasattr(settings, "web_search_provider")


class TestEmbeddingSettings:
Expand Down
15 changes: 9 additions & 6 deletions tests/test_setup_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,20 @@ mk_workspace "$WS"
# 2. JINA_API_KEY (skip)
# 3. Configure integrations? y
# 4. TAVILY_API_KEY my-tavily-key
# 5. Ollama? y
# 6. MCP server? y
# 7. Graph backend [1 = default neo4j]
# 8. Rotate auth tokens? y
printf '\n\ny\nmy-tavily-key\ny\ny\n\ny\n' | (
# 5. OLOSTEP_API_KEY (skip)
# 6. WEB_SEARCH_PROVIDER (default tavily)
# 7. Ollama? y
# 8. MCP server? y
# 9. Graph backend [1 = default neo4j]
# 10. Rotate auth tokens? y
printf '\n\ny\nmy-tavily-key\n\n\ny\ny\n\ny\n' | (
cd "$WS"
PATH="${WS}/stubs:${MINBIN}" bash ./atlas
) > "$WS/stdout" 2> "$WS/stderr"
status=$?
assert "exited with status 0" "[ $status -eq 0 ]"
assert "TAVILY_API_KEY was written" "grep -qE '^TAVILY_API_KEY=my-tavily-key$' '$WS/.env'"
assert "WEB_SEARCH_PROVIDER defaulted" "grep -qE '^WEB_SEARCH_PROVIDER=tavily$' '$WS/.env'"
assert "OLLAMA_ENABLED=true" "grep -qE '^OLLAMA_ENABLED=true$' '$WS/.env'"
assert "BEEVER_MCP_ENABLED=true" "grep -qE '^BEEVER_MCP_ENABLED=true$' '$WS/.env'"
assert "BEEVER_MCP_API_KEYS auto-generated" "grep -qE '^BEEVER_MCP_API_KEYS=mcp-' '$WS/.env'"
Expand Down Expand Up @@ -369,7 +372,7 @@ mk_workspace "$WS"
(
cd "$WS"
# Ensure neither key is in the environment
env -u GOOGLE_API_KEY -u JINA_API_KEY -u TAVILY_API_KEY \
env -u GOOGLE_API_KEY -u JINA_API_KEY -u TAVILY_API_KEY -u OLOSTEP_API_KEY -u WEB_SEARCH_PROVIDER \
ATLAS_HEALTH_POLL_TIMEOUT=0 PATH="${WS}/stubs:${MINBIN}" bash ./atlas --non-interactive
) > "$WS/stdout" 2> "$WS/stderr"
status=$?
Expand Down
Loading