Skip to content

Commit eca4586

Browse files
authored
Merge pull request #62 from planetf1/customheader
2 parents f4284d9 + 8fb262f commit eca4586

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,16 @@ maestro-knowledge/
442442
- `OPENAI_API_KEY`: Required for OpenAI embedding models
443443
- `MAESTRO_KNOWLEDGE_MCP_SERVER_URI`: MCP server URI for CLI tool
444444
- `MILVUS_URI`: Milvus connection URI. **Important**: Do not use quotes around the URI value in your `.env` file (e.g., `MILVUS_URI=http://localhost:19530` instead of `MILVUS_URI="http://localhost:19530"`).
445+
- `CUSTOM_EMBEDDING_HEADERS`: Custom headers for your embedding provider when using `embedding: custom_local`.
446+
**Important**: Due to shell parsing, the value **must be enclosed in single quotes** in your `.env` file to handle special characters correctly.
447+
- **Recommended format (JSON string):**
448+
```
449+
CUSTOM_EMBEDDING_HEADERS='{"API_SECRET_KEY": "your-secret-key", "Another-Header": "value"}'
450+
```
451+
- **Alternative format (key-value pairs):**
452+
```
453+
CUSTOM_EMBEDDING_HEADERS='API_SECRET_KEY=your-secret-key,Another-Header=value'
454+
```
445455
- Database-specific environment variables for Weaviate and Milvus connections
446456
447457
For detailed environment variable usage in CLI and MCP server, see their respective README files.

src/db/vector_db_milvus.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,37 @@ def _create_client(self) -> None:
113113
if original_milvus_uri:
114114
os.environ["MILVUS_URI"] = original_milvus_uri
115115

116+
def _parse_custom_headers(self) -> dict[str, str]:
117+
"""Parse CUSTOM_EMBEDDING_HEADERS environment variable into a dictionary."""
118+
headers_str = os.getenv("CUSTOM_EMBEDDING_HEADERS")
119+
if not headers_str:
120+
return {}
121+
122+
# Strip leading/trailing quotes that might come from .env files or shell exports
123+
if (headers_str.startswith('"') and headers_str.endswith('"')) or (
124+
headers_str.startswith("'") and headers_str.endswith("'")
125+
):
126+
headers_str = headers_str[1:-1]
127+
128+
try:
129+
# Try parsing as JSON first
130+
headers = json.loads(headers_str)
131+
if isinstance(headers, dict):
132+
return headers
133+
# If JSON parsing results in a non-dict (e.g. a string),
134+
# fall through to key-value parsing.
135+
except json.JSONDecodeError:
136+
# Not a valid JSON object, so fall back to key=value parsing
137+
pass
138+
139+
headers = {}
140+
for item in headers_str.split(","):
141+
# Split only on the first '=' to allow for '=' in the value
142+
key_value = item.split("=", 1)
143+
if len(key_value) == 2:
144+
headers[key_value[0].strip()] = key_value[1].strip()
145+
return headers
146+
116147
def _generate_embedding(self, text: str, embedding_model: str) -> list[float]:
117148
"""
118149
Generate embeddings for text using the specified model.
@@ -129,7 +160,6 @@ def _generate_embedding(self, text: str, embedding_model: str) -> list[float]:
129160

130161
client_kwargs = {}
131162
model_to_use = embedding_model
132-
133163
if embedding_model == "custom_local":
134164
custom_endpoint_url = os.getenv("CUSTOM_EMBEDDING_URL")
135165
if not custom_endpoint_url:
@@ -144,6 +174,11 @@ def _generate_embedding(self, text: str, embedding_model: str) -> list[float]:
144174
raise ValueError(
145175
"CUSTOM_EMBEDDING_MODEL must be set for 'custom_local' embedding."
146176
)
177+
178+
# Add custom headers if available
179+
custom_headers = self._parse_custom_headers()
180+
if custom_headers:
181+
client_kwargs["default_headers"] = custom_headers
147182
else:
148183
# Get OpenAI API key from environment
149184
api_key = os.getenv("OPENAI_API_KEY")
@@ -157,6 +192,7 @@ def _generate_embedding(self, text: str, embedding_model: str) -> list[float]:
157192
model_to_use = "text-embedding-ada-002"
158193

159194
client = openai.OpenAI(**client_kwargs)
195+
160196
response = client.embeddings.create(model=model_to_use, input=text)
161197

162198
return response.data[0].embedding

src/maestro_mcp/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,16 @@ The server respects the following environment variables:
356356
- `CUSTOM_EMBEDDING_API_KEY`: The API key for the custom embedding endpoint (optional, but recommended for authentication).
357357
- `CUSTOM_EMBEDDING_MODEL`: The model name for the custom embedding endpoint (required for `custom_local` embedding for Milvus).
358358
- `CUSTOM_EMBEDDING_VECTORSIZE`: The vector dimension for the `custom_local` embedding model (required when using `custom_local`).
359+
- `CUSTOM_EMBEDDING_HEADERS`: Custom headers for your embedding provider when using `embedding: custom_local`.
360+
**Important**: Due to shell parsing, the value **must be enclosed in single quotes** in your `.env` file to handle special characters correctly.
361+
- **Recommended format (JSON string):**
362+
```
363+
CUSTOM_EMBEDDING_HEADERS='{"API_SECRET_KEY": "your-secret-key", "Another-Header": "value"}'
364+
```
365+
- **Alternative format (key-value pairs):**
366+
```
367+
CUSTOM_EMBEDDING_HEADERS='API_SECRET_KEY=your-secret-key,Another-Header=value'
368+
```
359369
- Database-specific environment variables for Weaviate and Milvus connections
360370

361371
## Error Handling

start.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ start_http_server() {
201201
# Load .env file if it exists
202202
if [ -f "$SCRIPT_DIR/.env" ]; then
203203
print_status "Loading environment variables from .env file..."
204-
export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
204+
set -o allexport
205+
source "$SCRIPT_DIR/.env"
206+
set +o allexport
205207
fi
206208

207209
if [ -n "$CUSTOM_EMBEDDING_URL" ]; then
@@ -254,7 +256,9 @@ start_stdio_server() {
254256
# Load .env file if it exists
255257
if [ -f "$SCRIPT_DIR/.env" ]; then
256258
print_status "Loading environment variables from .env file..."
257-
export $(grep -v '^#' "$SCRIPT_DIR/.env" | xargs)
259+
set -o allexport
260+
source "$SCRIPT_DIR/.env"
261+
set +o allexport
258262
fi
259263

260264
# Test module import

tests/test_vector_db_milvus.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,57 @@ def test_delete_documents_raises_milvus_exception(
701701
with pytest.raises(MilvusException, match="Delete failed"):
702702
db.delete_documents(["1"])
703703

704+
def test_parse_custom_headers(self) -> None:
705+
"""Test the _parse_custom_headers method with various formats."""
706+
db = MilvusVectorDatabase()
707+
708+
# Test case 1: No environment variable set
709+
with patch.dict(os.environ, {}, clear=True):
710+
assert db._parse_custom_headers() == {}
711+
712+
# Test case 2: Simple key-value string
713+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": "KEY=VALUE"}):
714+
assert db._parse_custom_headers() == {"KEY": "VALUE"}
715+
716+
# Test case 3: Multiple key-value pairs
717+
with patch.dict(
718+
os.environ, {"CUSTOM_EMBEDDING_HEADERS": "KEY1=VALUE1,KEY2=VALUE2"}
719+
):
720+
assert db._parse_custom_headers() == {"KEY1": "VALUE1", "KEY2": "VALUE2"}
721+
722+
# Test case 4: Key-value with equals sign in value
723+
with patch.dict(
724+
os.environ, {"CUSTOM_EMBEDDING_HEADERS": "KEY=VALUE_WITH_EQUALS="}
725+
):
726+
assert db._parse_custom_headers() == {"KEY": "VALUE_WITH_EQUALS="}
727+
728+
# Test case 5: Simple JSON string
729+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": '{"KEY": "VALUE"}'}):
730+
assert db._parse_custom_headers() == {"KEY": "VALUE"}
731+
732+
# Test case 6: JSON string with multiple values
733+
with patch.dict(
734+
os.environ,
735+
{"CUSTOM_EMBEDDING_HEADERS": '{"KEY1": "VALUE1", "KEY2": "VALUE2"}'},
736+
):
737+
assert db._parse_custom_headers() == {"KEY1": "VALUE1", "KEY2": "VALUE2"}
738+
739+
# Test case 7: Key-value string with surrounding quotes to be stripped
740+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": '"KEY=VALUE"'}):
741+
assert db._parse_custom_headers() == {"KEY": "VALUE"}
742+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": "'KEY=VALUE'"}):
743+
assert db._parse_custom_headers() == {"KEY": "VALUE"}
744+
745+
# Test case 8: Key-value with spaces
746+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": " KEY = VALUE "}):
747+
assert db._parse_custom_headers() == {"KEY": "VALUE"}
748+
749+
# Test case 9: JSON that is not a dictionary (should be ignored)
750+
with patch.dict(os.environ, {"CUSTOM_EMBEDDING_HEADERS": '"just a string"'}):
751+
# This is valid JSON (a string), but not a dict, so our parser should fall back
752+
# to key-value parsing, which will fail and return an empty dict.
753+
assert db._parse_custom_headers() == {}
754+
704755

705756
if __name__ == "__main__":
706757
pytest.main()

0 commit comments

Comments
 (0)