File size: 7,230 Bytes
0017280
 
 
 
 
 
 
 
 
 
 
 
 
 
1cdb611
0017280
 
c2a725e
0017280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68a6539
0017280
 
 
 
 
770e251
0017280
 
 
 
5044f4c
0017280
 
 
 
f9f7706
0bb6123
f9f7706
0017280
 
 
 
 
 
ebd834c
 
 
 
 
0017280
 
 
 
 
76c97db
 
 
 
0017280
 
 
76c97db
0017280
 
 
 
 
1330e5c
 
0017280
1330e5c
 
 
 
 
 
 
 
 
fbd18c8
 
 
 
 
 
 
9093d8f
1330e5c
 
 
 
 
 
fbd18c8
 
1330e5c
0017280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c2a725e
0017280
 
 
 
 
9093d8f
f95cdb7
 
0017280
 
 
 
 
 
 
9093d8f
0017280
6d5b557
 
 
f16a878
0017280
 
 
 
 
 
 
 
9093d8f
 
 
 
 
0017280
 
 
 
 
 
 
 
 
 
 
 
f95cdb7
 
 
 
 
 
 
 
 
 
 
 
0017280
1330e5c
0017280
1330e5c
0017280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""Interactive setup wizard β€” generates config.yaml and .env."""

import getpass
import os
import sys
from pathlib import Path

import yaml


_ENV_KEY_MAP = {
    "openai": "OPENAI_API_KEY",
    "anthropic": "ANTHROPIC_API_KEY",
    "gemini": "GEMINI_API_KEY",
    "hugging-face": "HF_API_KEY",
}

_PROVIDER_CHOICES = {"1": "openai", "2": "anthropic", "3": "gemini", "4": "meta-llama"}


def generate_config(
    bot_name: str,
    domain: str,
    provider: str,
    model: str,
    web_search: bool,
) -> str:
    """Return a YAML string with all config sections."""
    config = {
        "chatbot": {
            "name": bot_name,
            "domain": domain,
        },
        "llm": {
            "provider": provider,
            "model": model,
            "temperature": 0.0,
            "max_tokens": 8192,
        },
        "api_keys": {
            "openai": "",
            "anthropic": "",
            "gemini": "",
            "meta-llama": "",
        },
        "embeddings": {
            "provider": "local",
            "openai_model": "text-embedding-3-small",
            "emb_model": "sentence-transformers/all-mpnet-base-v2",
        },
        "retrieval": {
            "chunk_size": 1000,
            "chunk_overlap": 100,
            "top_k": 20,
            "max_distance": 0.55,
            "max_context_chars": 12000,
        },
        "web_search": {
            "enabled": web_search,
            "backend": "semantic_scholar",
            "max_results": 5,
        },
        "query_understanding": {
            "enabled": True,
            "max_history": 6,
            "max_clarifications": 1,
        },
        "verification": {
            "enabled": True,
            "max_iterations": 3,
            "strict_mode": True,
        },
        "sql": {
            "enabled": True,
            "max_rows": 200,
        },
        "paths": {
            "knowledge_base": "knowledge_base",
            "vector_db": "chroma_db",
            "sql_db": "sql_db",
        },
    }
    return yaml.dump(config, default_flow_style=False, sort_keys=False)


def generate_env(provider: str, api_key: str, existing_env_path: str = None) -> str:
    """Return .env file content, merging with existing keys if present."""
    env_var = _ENV_KEY_MAP.get(provider, f"{provider.upper()}_API_KEY")

    # Preserve existing keys from a prior .env file
    existing: dict[str, str] = {}
    if existing_env_path and os.path.exists(existing_env_path):
        with open(existing_env_path, "r") as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith("#") and "=" in line:
                    k, v = line.split("=", 1)
                    v = v.strip()
                    # Unwrap one matched pair of quotes
                    if (v.startswith('"') and v.endswith('"')) or \
                       (v.startswith("'") and v.endswith("'")):
                        v = v[1:-1]
                    # Unescape previously escaped characters
                    v = v.replace('\\"', '"').replace('\\\\', '\\')
                    existing[k.strip()] = v

    # Update with the new key
    existing[env_var] = api_key

    lines = ["# Auto-generated by setup wizard"]
    for k, v in sorted(existing.items()):
        v_escaped = v.replace('\\', '\\\\').replace('"', '\\"')
        lines.append(f'{k}="{v_escaped}"')
    lines.append("")
    return "\n".join(lines)


def run_wizard():
    """Five-step interactive setup flow."""
    project_root = Path(__file__).resolve().parent

    print("=" * 50)
    print("  RAG Research Chatbot β€” Setup Wizard")
    print("=" * 50)
    print()

    # Step 1: Bot name
    bot_name = input("Step 1/5 β€” Bot name [Research Assistant]: ").strip()
    if not bot_name:
        bot_name = "Research Assistant"

    # Step 2: Domain
    domain = input("Step 2/5 β€” Domain / topic description: ").strip()
    if not domain:
        domain = "general research"

    # Step 3: Provider
    print("\nStep 3/5 β€” LLM provider:")
    print("  1) OpenAI")
    print("  2) Anthropic")
    print("  3) Google Gemini")
    print("  4) Meta Llama")
    provider_choice = input("Choose [1]: ").strip() or "1"
    provider = _PROVIDER_CHOICES.get(provider_choice, "openai")

    # Step 3b: API key
    api_key = getpass.getpass(f"Enter your {provider} API key: ")
    if not api_key:
        print(f"  Warning: No API key entered for {provider}.")
        print(f"  Set it later by editing .env or re-running: python setup.py")

    # Step 4: Model selection β€” fetch available models
    print(f"\nFetching available {provider} models...")
    try:
        from src.llm import list_models
        models = list_models(provider, api_key)
    except Exception:
        print(f"  Warning: Could not validate API key for {provider}. Using default model list.")
        fallback = {
            "openai": ["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano"],
            "anthropic": ["claude-sonnet-4-6", "claude-haiku-4-5", "claude-opus-4-6"],
            "gemini": ["gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash"],
            "meta": ["meta-llama/Llama-3.3-70B-Instruct"],
        }
        models = fallback.get(provider, ["default-model"])

    print("\nStep 4/5 β€” Choose a model:")
    for i, m in enumerate(models, 1):
        print(f"  {i}) {m}")
    model_choice = input(f"Choose [1]: ").strip() or "1"
    try:
        idx = int(model_choice) - 1
        if 0 <= idx < len(models):
            model = models[idx]
        else:
            model = models[0]
    except (ValueError, IndexError):
        model = models[0]

    # Step 5: Web search
    print("\nStep 5/5 β€” Enable web search (Semantic Scholar)?")
    print("  1) Yes")
    print("  2) No")
    ws_choice = input("Choose [1]: ").strip() or "1"
    web_search = ws_choice == "1"

    # Write config.yaml
    config_path = project_root / "config.yaml"
    if config_path.exists():
        overwrite = input(f"\n{config_path} already exists. Overwrite? [y/N]: ").strip().lower()
        if overwrite != 'y':
            print("  Keeping existing config.yaml.")
        else:
            config_str = generate_config(bot_name, domain, provider, model, web_search)
            config_path.write_text(config_str)
            print(f"\n  Wrote {config_path}")
    else:
        config_str = generate_config(bot_name, domain, provider, model, web_search)
        config_path.write_text(config_str)
        print(f"\n  Wrote {config_path}")

    # Write .env (merges with existing keys if present)
    env_path = project_root / ".env"
    env_str = generate_env(provider, api_key, existing_env_path=str(env_path))
    env_path.write_text(env_str)
    print(f"  Wrote {env_path}")

    # Create knowledge_base/ directory
    kb_dir = project_root / "knowledge_base"
    kb_dir.mkdir(exist_ok=True)
    print(f"  Created {kb_dir}/")

    # Next steps
    print("\n" + "=" * 50)
    print("  Setup complete! Next steps:")
    print("=" * 50)
    print(f"  1. Add documents to {kb_dir}/")
    print("  2. Run: python ingest.py")
    print("  3. Run: python app_cli.py  (or: streamlit run app_web.py)")
    print()


if __name__ == "__main__":
    run_wizard()