Skip to content

Commit 907b085

Browse files
authored
Merge branch 'main' into feat/small-context-chunking
2 parents 178b33c + 2d2df8a commit 907b085

39 files changed

Lines changed: 1529 additions & 241 deletions

File tree

.github/workflows/build-and-release.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
has_dockerhub_secrets: ${{ steps.check.outputs.has_dockerhub_secrets }}
2828
steps:
2929
- name: Checkout
30-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
30+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3131

3232
- name: Extract version from pyproject.toml
3333
id: version
@@ -55,7 +55,7 @@ jobs:
5555
runs-on: ubuntu-latest
5656
steps:
5757
- name: Checkout
58-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
58+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
5959

6060
- name: Free up disk space
6161
run: |
@@ -67,7 +67,7 @@ jobs:
6767
df -h
6868
6969
- name: Set up Docker Buildx
70-
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
70+
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
7171

7272
- name: Login to GitHub Container Registry
7373
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
@@ -84,7 +84,7 @@ jobs:
8484
password: ${{ secrets.DOCKER_PASSWORD }}
8585

8686
- name: Cache Docker layers
87-
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
87+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
8888
with:
8989
path: /tmp/.buildx-cache
9090
key: ${{ runner.os }}-buildx-regular-${{ github.sha }}
@@ -147,7 +147,7 @@ jobs:
147147
runs-on: ubuntu-latest
148148
steps:
149149
- name: Checkout
150-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
150+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
151151

152152
- name: Free up disk space
153153
run: |
@@ -159,7 +159,7 @@ jobs:
159159
df -h
160160
161161
- name: Set up Docker Buildx
162-
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
162+
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
163163

164164
- name: Login to GitHub Container Registry
165165
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
@@ -176,7 +176,7 @@ jobs:
176176
password: ${{ secrets.DOCKER_PASSWORD }}
177177

178178
- name: Cache Docker layers
179-
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
179+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
180180
with:
181181
path: /tmp/.buildx-cache-single
182182
key: ${{ runner.os }}-buildx-single-${{ github.sha }}

.github/workflows/build-dev.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
is_push_to_main: ${{ steps.check.outputs.is_push_to_main }}
4040
steps:
4141
- name: Checkout
42-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
42+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4343

4444
- name: Extract version from pyproject.toml
4545
id: version
@@ -85,10 +85,10 @@ jobs:
8585
image: ${{ steps.filter.outputs.image }}
8686
steps:
8787
- name: Checkout
88-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
88+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
8989

9090
- name: Detect image-affecting changes
91-
uses: dorny/paths-filter@15192bc058cc28a13dbf6cde61f19e18988b7af6 # v3
91+
uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3
9292
id: filter
9393
with:
9494
filters: |
@@ -111,7 +111,7 @@ jobs:
111111
runs-on: ubuntu-latest
112112
steps:
113113
- name: Checkout
114-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
114+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
115115

116116
- name: Skip notice
117117
if: needs.extract-version.outputs.is_push_to_main != 'true' && needs.changes.outputs.image != 'true'
@@ -129,7 +129,7 @@ jobs:
129129
130130
- name: Set up Docker Buildx
131131
if: needs.extract-version.outputs.is_push_to_main == 'true' || needs.changes.outputs.image == 'true'
132-
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
132+
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
133133

134134
- name: Login to GitHub Container Registry
135135
if: needs.extract-version.outputs.is_push_to_main == 'true'
@@ -148,7 +148,7 @@ jobs:
148148

149149
- name: Cache Docker layers
150150
if: needs.extract-version.outputs.is_push_to_main == 'true' || needs.changes.outputs.image == 'true'
151-
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
151+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
152152
with:
153153
path: /tmp/.buildx-cache-dev
154154
key: ${{ runner.os }}-buildx-dev-${{ github.sha }}
@@ -201,7 +201,7 @@ jobs:
201201
runs-on: ubuntu-latest
202202
steps:
203203
- name: Checkout
204-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
204+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
205205

206206
- name: Free up disk space
207207
run: |
@@ -213,7 +213,7 @@ jobs:
213213
df -h
214214
215215
- name: Set up Docker Buildx
216-
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
216+
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
217217

218218
- name: Login to GitHub Container Registry
219219
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
@@ -230,7 +230,7 @@ jobs:
230230
password: ${{ secrets.DOCKER_PASSWORD }}
231231

232232
- name: Cache Docker layers
233-
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
233+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
234234
with:
235235
path: /tmp/.buildx-cache-dev-single
236236
key: ${{ runner.os }}-buildx-dev-single-${{ github.sha }}

.github/workflows/docs-links.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ jobs:
1414
check-links:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
17+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1818
- name: Check relative markdown links
1919
run: python3 scripts/check_md_links.py

.github/workflows/test.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ jobs:
1919
runs-on: ubuntu-latest
2020
steps:
2121
- name: Checkout
22-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
22+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2323

2424
- name: Set up uv
25-
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
25+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
2626
with:
2727
enable-cache: true
2828

@@ -36,7 +36,7 @@ jobs:
3636
run: uv run pytest tests/ -v --cov=open_notebook --cov=api --cov-report=term-missing --cov-report=xml
3737

3838
- name: Upload coverage artifact
39-
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
39+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
4040
with:
4141
name: backend-coverage
4242
path: coverage.xml
@@ -46,10 +46,10 @@ jobs:
4646
runs-on: ubuntu-latest
4747
steps:
4848
- name: Checkout
49-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
49+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
5050

5151
- name: Set up uv
52-
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
52+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
5353
with:
5454
enable-cache: true
5555

@@ -67,10 +67,10 @@ jobs:
6767
runs-on: ubuntu-latest
6868
steps:
6969
- name: Checkout
70-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
70+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
7171

7272
- name: Set up uv
73-
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
73+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
7474
with:
7575
enable-cache: true
7676

@@ -91,10 +91,10 @@ jobs:
9191
working-directory: frontend
9292
steps:
9393
- name: Checkout
94-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
94+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
9595

9696
- name: Set up Node.js
97-
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
97+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
9898
with:
9999
node-version: 22
100100
cache: npm
@@ -107,7 +107,7 @@ jobs:
107107
run: npm run test:coverage
108108

109109
- name: Upload coverage artifact
110-
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
110+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
111111
with:
112112
name: frontend-coverage
113113
path: frontend/coverage/
@@ -120,10 +120,10 @@ jobs:
120120
working-directory: frontend
121121
steps:
122122
- name: Checkout
123-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
123+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
124124

125125
- name: Set up Node.js
126-
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
126+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
127127
with:
128128
node-version: 22
129129
cache: npm
@@ -143,10 +143,10 @@ jobs:
143143
working-directory: frontend
144144
steps:
145145
- name: Checkout
146-
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
146+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
147147

148148
- name: Set up Node.js
149-
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
149+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
150150
with:
151151
node-version: 22
152152
cache: npm

api/models.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,35 @@ class RecentlyViewedResponse(BaseModel):
3636

3737

3838
# Search models
39-
class SearchRequest(BaseModel):
39+
class NotebookScopeMixin(BaseModel):
40+
"""Optional notebook scope shared by Search and Ask requests (#574, #87).
41+
42+
Both `notebook_id` (single, the shape #574 proposed and existing clients
43+
already send) and `notebook_ids` (several) are accepted; `scope_notebook_ids`
44+
merges them. An empty scope means the whole knowledge base.
45+
"""
46+
47+
notebook_id: Optional[str] = Field(
48+
None, description="Restrict results to a single notebook"
49+
)
50+
notebook_ids: Optional[List[str]] = Field(
51+
None,
52+
max_length=50,
53+
description="Restrict results to these notebooks (omit or empty for all)",
54+
)
55+
56+
@property
57+
def scope_notebook_ids(self) -> List[str]:
58+
# Keep empty strings so validation rejects them instead of silently
59+
# widening the scope to the whole knowledge base.
60+
merged: List[str] = []
61+
for nb_id in [self.notebook_id, *(self.notebook_ids or [])]:
62+
if nb_id is not None and nb_id not in merged:
63+
merged.append(nb_id)
64+
return merged
65+
66+
67+
class SearchRequest(NotebookScopeMixin):
4068
query: str = Field(..., description="Search query")
4169
type: Literal["text", "vector"] = Field("text", description="Search type")
4270
limit: int = Field(100, description="Maximum number of results", ge=1, le=1000)
@@ -53,7 +81,7 @@ class SearchResponse(BaseModel):
5381
search_type: str = Field(..., description="Type of search performed")
5482

5583

56-
class AskRequest(BaseModel):
84+
class AskRequest(NotebookScopeMixin):
5785
question: str = Field(..., description="Question to ask the knowledge base")
5886
strategy_model: str = Field(..., description="Model ID for query strategy")
5987
answer_model: str = Field(..., description="Model ID for individual answers")

api/routers/search.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import json
2-
from typing import AsyncGenerator
2+
from typing import AsyncGenerator, List
33

44
from fastapi import APIRouter, HTTPException
55
from fastapi.responses import StreamingResponse
66
from loguru import logger
77

88
from api.models import AskRequest, AskResponse, SearchRequest, SearchResponse
99
from open_notebook.ai.models import Model, model_manager
10-
from open_notebook.domain.notebook import text_search, vector_search
10+
from open_notebook.domain.notebook import (
11+
resolve_notebook_scope,
12+
text_search,
13+
vector_search,
14+
)
1115
from open_notebook.exceptions import (
1216
DatabaseOperationError,
1317
InvalidInputError,
@@ -22,6 +26,8 @@
2226
async def search_knowledge_base(search_request: SearchRequest):
2327
"""Search the knowledge base using text or vector search."""
2428
try:
29+
notebook_ids = await resolve_notebook_scope(search_request.scope_notebook_ids)
30+
2531
if search_request.type == "vector":
2632
# Check if embedding model is available for vector search
2733
if not await model_manager.get_embedding_model():
@@ -36,6 +42,7 @@ async def search_knowledge_base(search_request: SearchRequest):
3642
source=search_request.search_sources,
3743
note=search_request.search_notes,
3844
minimum_score=search_request.minimum_score,
45+
notebook_ids=notebook_ids,
3946
)
4047
else:
4148
# Text search
@@ -44,6 +51,7 @@ async def search_knowledge_base(search_request: SearchRequest):
4451
results=search_request.limit,
4552
source=search_request.search_sources,
4653
note=search_request.search_notes,
54+
notebook_ids=notebook_ids,
4755
)
4856

4957
return SearchResponse(
@@ -67,7 +75,11 @@ async def search_knowledge_base(search_request: SearchRequest):
6775

6876

6977
async def stream_ask_response(
70-
question: str, strategy_model: Model, answer_model: Model, final_answer_model: Model
78+
question: str,
79+
strategy_model: Model,
80+
answer_model: Model,
81+
final_answer_model: Model,
82+
notebook_ids: List[str],
7183
) -> AsyncGenerator[str, None]:
7284
"""Stream the ask response as Server-Sent Events."""
7385
try:
@@ -76,7 +88,7 @@ async def stream_ask_response(
7688
# LangGraph accepts a partial state dict at runtime, but its typed
7789
# overloads require the full state type (langgraph typing limitation).
7890
async for chunk in ask_graph.astream( # type: ignore[call-overload]
79-
input=dict(question=question),
91+
input=dict(question=question, notebook_ids=notebook_ids),
8092
config=dict(
8193
configurable=dict(
8294
strategy_model=strategy_model.id,
@@ -124,6 +136,10 @@ async def stream_ask_response(
124136
async def ask_knowledge_base(ask_request: AskRequest):
125137
"""Ask the knowledge base a question using AI models."""
126138
try:
139+
# Cheapest check first: a malformed or unknown scope fails before any
140+
# model lookup or embedding check can mask it.
141+
notebook_ids = await resolve_notebook_scope(ask_request.scope_notebook_ids)
142+
127143
# Validate models exist
128144
strategy_model = await Model.get(ask_request.strategy_model)
129145
answer_model = await Model.get(ask_request.answer_model)
@@ -155,7 +171,11 @@ async def ask_knowledge_base(ask_request: AskRequest):
155171
# For streaming response
156172
return StreamingResponse(
157173
stream_ask_response(
158-
ask_request.question, strategy_model, answer_model, final_answer_model
174+
ask_request.question,
175+
strategy_model,
176+
answer_model,
177+
final_answer_model,
178+
notebook_ids,
159179
),
160180
media_type="text/event-stream",
161181
headers={
@@ -178,6 +198,10 @@ async def ask_knowledge_base(ask_request: AskRequest):
178198
async def ask_knowledge_base_simple(ask_request: AskRequest):
179199
"""Ask the knowledge base a question and return a simple response (non-streaming)."""
180200
try:
201+
# Cheapest check first: a malformed or unknown scope fails before any
202+
# model lookup or embedding check can mask it.
203+
notebook_ids = await resolve_notebook_scope(ask_request.scope_notebook_ids)
204+
181205
# Validate models exist
182206
strategy_model = await Model.get(ask_request.strategy_model)
183207
answer_model = await Model.get(ask_request.answer_model)
@@ -211,7 +235,7 @@ async def ask_knowledge_base_simple(ask_request: AskRequest):
211235
# LangGraph accepts a partial state dict at runtime, but its typed
212236
# overloads require the full state type (langgraph typing limitation).
213237
async for chunk in ask_graph.astream( # type: ignore[call-overload]
214-
input=dict(question=ask_request.question),
238+
input=dict(question=ask_request.question, notebook_ids=notebook_ids),
215239
config=dict(
216240
configurable=dict(
217241
strategy_model=strategy_model.id,

0 commit comments

Comments
 (0)