Skip to content

Commit 6f98e2b

Browse files
committed
feat: add Gemini provider docs, fix settings path, add model diagnostics
Update README with Gemini configuration examples and provider table. Simplify findSettingsFile to always use ~/.solenoid/ instead of walking the directory tree. Add startup logging for resolved model name and API key presence, and warn on empty Gemini responses.
1 parent c256f27 commit 6f98e2b

File tree

4 files changed

+53
-36
lines changed

4 files changed

+53
-36
lines changed

README.md

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ See [Development](#development) section below for building from source with Poet
3333
- **Web Research**: Brave Search integration for real-time web queries
3434
- **MCP Support**: Model Context Protocol for extensible tool integration (stdio and HTTP servers)
3535
- **Local Memory System**: SQLite + FTS5 + sqlite-vec for hybrid semantic/keyword search with BGE reranking
36-
- **Configurable Models**: Support for Ollama models via LiteLLM with automatic model pulling
36+
- **Configurable Models**: Support for Gemini (default) and Ollama models via Google ADK
3737
- **Customizable Prompts**: All agent prompts configurable via YAML
3838
- **In-App Settings Editor**: Edit configuration via `/settings` command with YAML validation
3939
- **Slash Commands**: Extensible command system for quick actions (`/settings`, `/help`, `/clear`)
@@ -178,31 +178,54 @@ All configuration is managed through `app_settings.yaml` in the project root.
178178

179179
### Model Configuration
180180

181+
Solenoid supports multiple model providers. Gemini is the default and requires no local infrastructure.
182+
183+
#### Gemini (Default)
184+
185+
Set your API key as an environment variable:
186+
187+
```bash
188+
export GOOGLE_GENAI_API_KEY="your-api-key"
189+
# or alternatively:
190+
export GEMINI_API_KEY="your-api-key"
191+
```
192+
181193
```yaml
182194
models:
183195
default:
184-
name: "ministral-3:8b"
185-
provider: "ollama_chat"
196+
name: "gemini-3-flash-preview"
197+
provider: "gemini"
186198
context_length: 128000
187-
agent:
199+
```
200+
201+
#### Ollama (Local Inference)
202+
203+
For fully local inference using [Ollama](https://ollama.com/):
204+
205+
```yaml
206+
models:
207+
default:
188208
name: "ministral-3:8b"
209+
provider: "ollama_chat"
189210
context_length: 128000
190-
extractor:
191-
name: "ministral-3:8b"
192211
```
193212
194-
**Model Roles:**
213+
If a configured Ollama model is not found locally, the application automatically attempts to pull it. Uses model names from the [Ollama library](https://ollama.com/library).
214+
215+
#### Model Roles
216+
195217
- `default`: Fallback model for unspecified roles
196218
- `agent`: Used by all agent roles (requires function calling support)
197219
- `extractor`: Used for memory extraction
198220

199-
**Model Requirements:**
200-
- Models used for the `agent` role must support **function calling** (tool use)
201-
- Recommended: `ministral-3:8b`, `qwen3:8b`, `llama3.1`, or similar function-calling capable models
202-
- Uses Ollama model names from the [Ollama library](https://ollama.com/library)
221+
#### Supported Providers
222+
223+
| Provider | Config Value | Notes |
224+
|----------|-------------|-------|
225+
| Gemini | `gemini` | Default. Requires `GOOGLE_GENAI_API_KEY` or `GEMINI_API_KEY` env var |
226+
| Ollama | `ollama_chat` | Local inference. Requires running Ollama server |
203227

204-
**Automatic Model Pulling:**
205-
If a configured model is not found in your local Ollama instance, the application automatically attempts to pull it when the agent starts.
228+
Models used for the `agent` role must support **function calling** (tool use).
206229

207230
### Search Configuration
208231

@@ -518,7 +541,7 @@ The executable will be created at `dist/solenoid`. This binary replicates the be
518541

519542
- Python 3.11+
520543
- Poetry (for dependency management)
521-
- Ollama (for local LLM inference)
544+
- Ollama (only required for `ollama_chat` provider)
522545

523546
### Key Dependencies
524547

src/agents/planning.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ try {
102102
}
103103

104104
const modelName = settings ? getAdkModelName('planning_agent', settings) : 'gemini-2.5-flash';
105+
agentLogger.info(`[Planning] Resolved model: ${modelName}, provider: ${settings?.models?.default?.provider ?? 'unknown'}`);
106+
107+
if (settings?.models?.default?.provider === 'gemini') {
108+
const hasKey = !!(process.env.GOOGLE_GENAI_API_KEY || process.env.GEMINI_API_KEY);
109+
agentLogger.info(`[Planning] Gemini API key present: ${hasKey}`);
110+
if (!hasKey) {
111+
agentLogger.warn('[Planning] No Gemini API key found. Set GOOGLE_GENAI_API_KEY or GEMINI_API_KEY');
112+
}
113+
}
105114

106115
const customPrompt = settings ? getAgentPrompt('planning_agent', settings) : undefined;
107116

src/agents/runner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ export async function* runAgent(
167167
yield { type: 'done' };
168168
return;
169169
}
170-
agentLogger.debug('[Runner] Skipping empty final event, continuing...');
170+
agentLogger.warn(
171+
`[Runner] Empty final event from ${event.author} — model may have failed silently (auth error? invalid model name?). Event: ${JSON.stringify({ id: event.id, role: event.content?.role, parts: event.content?.parts?.length ?? 0, actions: event.actions })}`
172+
);
171173
}
172174
}
173175

src/config/settings.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,10 @@ let cachedSettings: AppSettings | null = null;
2828
let cachedRawSettings: Record<string, unknown> | null = null;
2929
let settingsPath: string | null = null;
3030

31-
export function findSettingsFile(startDir: string = process.cwd()): string | null {
32-
let dir = startDir;
33-
const root = resolve('/');
34-
35-
while (dir !== root) {
36-
const candidate = resolve(dir, DEFAULT_SETTINGS_FILENAME);
37-
if (existsSync(candidate)) {
38-
return candidate;
39-
}
40-
dir = dirname(dir);
41-
}
42-
43-
const rootCandidate = resolve(root, DEFAULT_SETTINGS_FILENAME);
44-
if (existsSync(rootCandidate)) {
45-
return rootCandidate;
46-
}
47-
48-
// Check ~/.solenoid/ as final fallback
49-
const solenoidCandidate = resolve(homedir(), SOLENOID_CONFIG_DIR, DEFAULT_SETTINGS_FILENAME);
50-
if (existsSync(solenoidCandidate)) {
51-
return solenoidCandidate;
31+
export function findSettingsFile(): string | null {
32+
const configPath = resolve(homedir(), SOLENOID_CONFIG_DIR, DEFAULT_SETTINGS_FILENAME);
33+
if (existsSync(configPath)) {
34+
return configPath;
5235
}
5336

5437
return null;

0 commit comments

Comments
 (0)