Skip to content
Merged
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
9 changes: 5 additions & 4 deletions apps/docs/connectors/s3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: "Connect Amazon S3 or S3-compatible storage to sync files into your
icon: "aws"
---

Connect Amazon S3 buckets or S3-compatible storage services (MinIO, DigitalOcean Spaces, Cloudflare R2) to sync files into your Supermemory knowledge base.
Connect Amazon S3 buckets or S3-compatible storage services (MinIO, DigitalOcean Spaces, Cloudflare R2, Tigris) to sync files into your Supermemory knowledge base.

<Note>
The S3 connector requires a **Scale Plan** or higher. You can also create S3 connections directly from the [Supermemory Console](https://console.supermemory.ai).
Expand Down Expand Up @@ -78,7 +78,7 @@ For S3, provider-specific connection fields are passed inside the top-level `met
| `accessKeyId` | `metadata.accessKeyId` | Yes | AWS access key ID or S3-compatible service key |
| `secretAccessKey` | `metadata.secretAccessKey` | Yes | AWS secret access key |
| `bucket` | `metadata.bucket` | Yes | S3 bucket name |
| `region` | `metadata.region` | Yes | AWS region (e.g., `us-east-1`). Use `auto` for Cloudflare R2. |
| `region` | `metadata.region` | Yes | AWS region (e.g., `us-east-1`). Use `auto` for S3-compatible providers that don't expose AWS-style regions (MinIO, R2, Tigris). |
| `endpoint` | `metadata.endpoint` | No | Custom endpoint for S3-compatible services |
| `prefix` | `metadata.prefix` | No | Key prefix filter (e.g., `documents/`) |
| `containerTagRegex` | `metadata.containerTagRegex` | No | Regex to extract container tags from file paths |
Expand All @@ -91,7 +91,7 @@ In the Python SDK, use `container_tags` for the top-level option, but keep S3 me

## S3-Compatible Services

Use `metadata.endpoint` to connect to S3-compatible storage:
Use `metadata.endpoint` to connect to S3-compatible storage. These services don't use AWS-style regions, so set `metadata.region` to `auto` — the value is still required for request signing but the service ignores it.

```typescript
// MinIO
Expand All @@ -100,7 +100,7 @@ const connection = await client.connections.create('s3', {
accessKeyId: 'minio-key',
secretAccessKey: 'minio-secret',
bucket: 'my-bucket',
region: 'us-east-1',
region: 'auto',
endpoint: 'https://minio.example.com'
},
containerTags: ['minio-sync']
Expand All @@ -113,6 +113,7 @@ Common S3-compatible endpoint values:
|---------|----------------------|-------------------|
| DigitalOcean Spaces | `https://nyc3.digitaloceanspaces.com` | `nyc3` |
| Cloudflare R2 | `https://<account-id>.r2.cloudflarestorage.com` | `auto` |
| Tigris | `https://t3.storage.dev` | `auto` |

Cloudflare R2 example:

Expand Down
16 changes: 15 additions & 1 deletion apps/web/app/api/onboarding/extract-content/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,22 @@ interface ExaApiResponse {
results: ExaContentResult[]
}

const exaApiKey = process.env.EXA_API_KEY
if (!exaApiKey) {
console.error(
"EXA_API_KEY is not configured; /api/onboarding/extract-content will return 503",
)
}

export async function POST(request: Request) {
try {
if (!exaApiKey) {
return Response.json(
{ error: "Content extraction is unavailable" },
{ status: 503 },
)
}

const { urls } = await request.json()

if (!Array.isArray(urls) || urls.length === 0) {
Expand All @@ -30,7 +44,7 @@ export async function POST(request: Request) {
const response = await fetch("https://api.exa.ai/contents", {
method: "POST",
headers: {
"x-api-key": process.env.EXA_API_KEY ?? "",
"x-api-key": exaApiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ async def add_system_prompt(
},
)

if not memories:
return messages

if system_prompt_exists:
logger.debug("Added memories to existing system prompt")
return [
Expand Down
59 changes: 59 additions & 0 deletions packages/openai-sdk-python/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,65 @@ async def test_existing_system_prompt_enhancement(
assert "User prefers Python" in system_message["content"]


@pytest.mark.asyncio
async def test_empty_memories_do_not_modify_messages(
self, mock_async_openai_client, mock_openai_response
):
"""No system prompt should be injected when the memory lookup returns nothing.

Previously, ``add_system_prompt`` would still modify messages even when
``memories`` resolved to an empty string: it appended `` \\n `` (whitespace) to
any existing system prompt, or prepended a new ``{"role": "system", "content": ""}``
message when none existed. Both outcomes pollute the conversation context with
meaningless tokens and can confuse the downstream model.
"""
original_create = AsyncMock(return_value=mock_openai_response)
mock_async_openai_client.chat.completions.create = original_create

empty_response = {
"profile": {"static": [], "dynamic": []},
"searchResults": {"results": []},
}

with patch.dict(os.environ, {"SUPERMEMORY_API_KEY": "test-key"}):
with patch("supermemory_openai.middleware.supermemory_profile_search") as mock_search:
mock_search.return_value = Mock()
mock_search.return_value.profile = empty_response["profile"]
mock_search.return_value.search_results = empty_response["searchResults"]

wrapped_client = with_supermemory(
mock_async_openai_client,
OpenAIMiddlewareOptions(
container_tag="user-123", custom_id="test-conv", mode="full"
),
)

# Case 1: no pre-existing system prompt — no system message should be prepended
user_only_messages = [{"role": "user", "content": "Hello"}]
await wrapped_client.chat.completions.create(
model="gpt-4", messages=user_only_messages
)
sent_messages = original_create.call_args[1]["messages"]
assert sent_messages == user_only_messages, (
"An empty-memory response must not prepend a blank system message"
)

original_create.reset_mock()

# Case 2: existing system prompt — it must not be modified
with_system = [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Hello"},
]
await wrapped_client.chat.completions.create(
model="gpt-4", messages=with_system
)
sent_messages = original_create.call_args[1]["messages"]
assert sent_messages[0]["content"] == "You are helpful.", (
"An empty-memory response must not append whitespace to the existing system prompt"
)


class TestMemoryStorage:
"""Test memory storage functionality."""

Expand Down
Loading