Skip to content

Commit 3695e1d

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into feat/api-key
2 parents 96c1dd9 + 1dc3fac commit 3695e1d

64 files changed

Lines changed: 1044 additions & 1853 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

surfsense_backend/alembic/versions/166_add_chunk_char_spans.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

surfsense_backend/app/agents/chat/multi_agent_chat/main_agent/middleware/kb_persistence/middleware.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from __future__ import annotations
2020

21+
import asyncio
2122
import logging
2223
from datetime import UTC, datetime
2324
from typing import Any
@@ -57,8 +58,9 @@
5758
FolderRevision,
5859
shielded_async_session,
5960
)
60-
from app.indexing_pipeline.cache.cached_indexing import build_chunk_embeddings
61+
from app.indexing_pipeline.document_chunker import chunk_text
6162
from app.utils.document_converters import (
63+
embed_texts,
6264
generate_content_hash,
6365
generate_unique_identifier_hash,
6466
)
@@ -232,23 +234,24 @@ async def _create_document(
232234
session.add(doc)
233235
await session.flush()
234236

235-
summary_embedding, chunk_embeddings = await build_chunk_embeddings(
236-
content, use_code_chunker=False
237-
)
237+
summary_embedding = (await asyncio.to_thread(embed_texts, [content]))[0]
238238
doc.embedding = summary_embedding
239-
session.add_all(
240-
[
241-
Chunk(
242-
document_id=doc.id,
243-
content=sl.text,
244-
embedding=embedding,
245-
position=i,
246-
start_char=sl.start_char,
247-
end_char=sl.end_char,
248-
)
249-
for i, (sl, embedding) in enumerate(chunk_embeddings)
250-
]
251-
)
239+
chunks = chunk_text(content)
240+
if chunks:
241+
chunk_embeddings = await asyncio.to_thread(embed_texts, chunks)
242+
session.add_all(
243+
[
244+
Chunk(
245+
document_id=doc.id,
246+
content=text,
247+
embedding=embedding,
248+
position=i,
249+
)
250+
for i, (text, embedding) in enumerate(
251+
zip(chunks, chunk_embeddings, strict=True)
252+
)
253+
]
254+
)
252255
return doc
253256

254257

@@ -284,25 +287,26 @@ async def _update_document(
284287
search_space_id,
285288
)
286289

287-
summary_embedding, chunk_embeddings = await build_chunk_embeddings(
288-
content, use_code_chunker=False
289-
)
290+
summary_embedding = (await asyncio.to_thread(embed_texts, [content]))[0]
290291
document.embedding = summary_embedding
291292

292293
await session.execute(delete(Chunk).where(Chunk.document_id == document.id))
293-
session.add_all(
294-
[
295-
Chunk(
296-
document_id=document.id,
297-
content=sl.text,
298-
embedding=embedding,
299-
position=i,
300-
start_char=sl.start_char,
301-
end_char=sl.end_char,
302-
)
303-
for i, (sl, embedding) in enumerate(chunk_embeddings)
304-
]
305-
)
294+
chunks = chunk_text(content)
295+
if chunks:
296+
chunk_embeddings = await asyncio.to_thread(embed_texts, chunks)
297+
session.add_all(
298+
[
299+
Chunk(
300+
document_id=document.id,
301+
content=text,
302+
embedding=embedding,
303+
position=i,
304+
)
305+
for i, (text, embedding) in enumerate(
306+
zip(chunks, chunk_embeddings, strict=True)
307+
)
308+
]
309+
)
306310
return document
307311

308312

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,42 @@
11
<citations>
2-
Citations reach the answer through three channels. Use whichever applies, and
3-
never invent ids you didn't see: ids are matched exactly, so a wrong one
4-
silently breaks the link — when in doubt, omit. Always write a citation as
5-
plain `[citation:…]` brackets — no markdown links, no footnote numbers, no
6-
parentheses.
2+
Citations reach the answer through two channels. Use whichever applies — and
3+
never invent ids you didn't see. Citation ids are resolved by exact-match
4+
lookup; a wrong id silently breaks the link, so when in doubt, omit.
75

8-
### Channel A — web_search chunk blocks injected this turn
6+
### Channel A — chunk blocks injected this turn
97
When `web_search` returns `<document>` / `<chunk id='…'>` blocks in this
10-
turn, the chunk `id` is the result's URL:
8+
turn:
119

12-
1. For each factual statement taken from a chunk, add `[citation:<url>]`
13-
using the **exact** id from a visible `<chunk id='…'>` tag. Copy the
14-
URL verbatim; do not retype it from memory.
15-
2. Multiple chunks → `[citation:url1], [citation:url2]` (comma-separated,
10+
1. For each factual statement taken from those chunks, add
11+
`[citation:chunk_id]` using the **exact** id from a visible
12+
`<chunk id='…'>` tag. Copy digit-for-digit (or the URL verbatim);
13+
do not retype from memory.
14+
2. `<document_id>` is the parent doc id, **not** a citation source —
15+
only ids inside `<chunk id='…'>` count.
16+
3. Multiple chunks → `[citation:id1], [citation:id2]` (comma-separated,
1617
each id copied individually).
17-
3. Never invent, normalise, or guess at a URL; if unsure, omit.
18+
4. Never invent, normalise, or guess at adjacent ids; if unsure, omit.
19+
5. Plain brackets only — no markdown links, no footnote numbering.
1820

1921
### Channel B — citations relayed by a `task` specialist
20-
A `task(...)` tool message may contain `[citation:…]` markers the
21-
specialist already attached to its prose — line citations
22-
(`[citation:d<id>#L<a>-<b>]`) or chunk ids (`[citation:N]`). The
23-
specialist read the underlying document and tied each marker to a
24-
passage; you didn't. So:
22+
A `task(...)` tool message may contain `[citation:<chunk_id>]` markers
23+
the specialist already attached to its prose. The specialist saw the
24+
underlying `<chunk id='…'>` blocks; you didn't. So:
2525

2626
1. **Preserve those markers verbatim** in your final answer — do not
2727
reformat, renumber, drop, or wrap them in markdown links. When you
2828
paraphrase a specialist sentence, copy the marker character-for-
29-
character; do not regenerate it from memory (LLMs reliably corrupt
30-
nearby digits).
29+
character; do not regenerate the id from memory (LLMs reliably
30+
corrupt nearby digits).
3131
2. Keep each marker attached to the sentence the specialist attached
3232
it to.
3333
3. Do **not** add new `[citation:…]` markers of your own to a
3434
specialist's prose; if a fact has no marker, the specialist
35-
couldn't tie it to a source and neither can you.
35+
couldn't tie it to a chunk and neither can you.
3636
4. When a specialist returns JSON, the citation markers live inside
3737
the prose-bearing fields (e.g. a summary or excerpt). Pull them
3838
along with the surrounding sentence when you quote.
3939

40-
### Channel C — your knowledge base (search hits and `read_file`)
41-
Knowledge-base facts are cited by line range using the document id:
42-
`[citation:d<document_id>#L<start>-<end>]` (a single line is `#L<n>-<n>`).
43-
44-
1. `search_knowledge_base` prints a ready `[citation:d…#L…-…]` token above each
45-
matched passage. When that passage supports your point, copy the token
46-
verbatim — that is the entire citation.
47-
2. When you `read_file` a `/documents/...` path, its header gives the
48-
`<document_id>` and an optional `<matched_lines>` pointer, and the body is
49-
shown with line numbers; cite the lines you actually used. Use `read_file`
50-
when you need more context than a search passage shows.
51-
3. Copy document ids and line numbers exactly as shown — never estimate,
52-
shift, or invent them.
53-
4. Older documents without a numbered body instead show `<chunk id='N'>`
54-
blocks; cite those with `[citation:N]`, copying the id exactly.
55-
56-
If none of these channels surfaces a citable source this turn, do not
57-
fabricate citations.
40+
If neither channel surfaces citation markers this turn, do not fabricate
41+
them.
5842
</citations>

0 commit comments

Comments
 (0)