NestJS 中的 Criteria 模式:客戶端可能要求的是一份檔案,而不是一個簽名

Back
Category : News



NestJS 中的 Criteria 模式

單一方式來過濾、排序與分頁任何清單。



五個參數與一個 find()

貫穿本文的範例是一個圖書館目錄。一本書會儲存以下內容:

// src/book/book.schema.ts
@Schema({ timestamps: true })
export class Book {
  @Prop() title: string;
  @Prop({ type: Types.ObjectId, ref: "Author" }) author: Types.ObjectId;
  @Prop() publishedAt: Date;
  @Prop() copies: number; // copies on the shelf
  @Prop() available: boolean;
  @Prop() acquisitionPrice: number; // what it cost us: internal, never published
}

Enter fullscreen mode

Exit fullscreen mode

作者的名字不在這裡:它存在於 authors 集合中,位於該參照的另一端。而使用目錄的畫面是一張帶有搜尋框、逐欄過濾器與分頁的表格。

提供資料的端點只寫一次,並透過累積方式成長。它一開始回傳固定排序的一頁資料,等到表格擁有所有過濾器時,它就變成這樣:

// src/book/book.controller.ts
@Controller("books")
export class BookController {
  constructor(
    @InjectModel(Book.name) private readonly model: Model<BookDocument>,
  ) {}

  @Get()
  async getAll(
    @Query("title") title?: string,
    @Query("available") available?: string,
    @Query("minCopies") minCopies?: string,
    @Query("sortBy") sortBy?: string,
    @Query("page") page?: string,
  ) {
    const filter: FilterQuery<BookDocument> = {};

    if (title) {
      filter.title = { $regex: title, $options: "i" };
    }

    if (available) {
      filter.available = available === "true";
    }

    if (minCopies) {
      filter.copies = { $gte: Number(minCopies) };
    }

    const current = Number(page ?? 1);

    const [items, total] = await Promise.all([
      this.model
        .find(filter)
        .sort({ [sortBy ?? "createdAt"]: -1 })
        .skip((current - 1) * 20)
        .limit(20),
      this.model.countDocuments(filter),
    ]);

    return { items: items, total: total, page: current };
  }
}

Enter fullscreen mode

Exit fullscreen mode

該方法內有一些正確的決策:總數來自與項目相同的過濾器,因此分頁不會自相矛盾,而且兩個查詢是平行執行的。像這樣撰寫的清單可以穩定運行多年而不發生事故,其行為並非本文打算修正的內容。

值得衡量的是最終寫在檔案外面的東西。端點的簽名與呼叫它所需的 URL 共同構成一份合約:

GET /books?title=dune&available=true&minCopies=3&sortBy=publishedAt&page=2

Enter fullscreen mode

Exit fullscreen mode

這份合約沒有在任何地方宣告,卻已經在正式環境中使用:當有人在工單中分享該 URL 或將其寫入匯入腳本的那一刻,查詢字串中的五個名稱就有了儲存庫外部的消費者。而它所使用的詞彙並非目錄的詞彙,而是集合的詞彙:sortBy=publishedAt 完全依照資料庫的稱呼來命名文件欄位,minCopies 也鎖定了未出現在名稱中的運算子,而讀到 title=dune 的人無法判斷它是在尋找精確比對還是部分比對,因為這只寫在 if 裡面。



這篇文章的走向

本文所建構的是 Criteria 模式:一個描述清單的物件——什麼被過濾、如何排序、哪一頁——從客戶端旅行到儲存庫,並被翻譯兩次,在每個邊界各一次。在分析之前先看看結果是值得的,因為接下來的一切都是為了證明為什麼是這種形狀而不是另一種。

同樣的表格,呼叫同樣的端點,會像這樣被請求:

GET /books
  ?filters[0][field]=title&filters[0][operator]=CONTAINS&filters[0][value][0]=dune
  &filters[1][field]=authorName&filters[1][operator]=EQUAL&filters[1][value][0]=Herbert
  &order[by]=publishedAt&order[type]=DESC
  &page=2&pageSize=20

Enter fullscreen mode

Exit fullscreen mode

回應會帶有頁面以及繪製分頁器所需的資訊:

{ "items": [], "totalItems": 143, "totalPages": 8, "pageSize": 20 }

Enter fullscreen mode

Exit fullscreen mode

而控制器內不再有任何欄位名稱:

// src/book/infrastructure/nest/book.controller.ts
@Get()
async getAll(
  @Query() request: CriteriaRequest,
): Promise<PaginationResponse<BookResponse>> {
  const useCase = new GetAllBooks(this.repository, new BookCriteriaRequestMapper());

  return await useCase.execute({ request: request });
}

Enter fullscreen mode

Exit fullscreen mode

將兩個 URL 並排在一起時,不用閱讀伺服器就能看出四個差異:

  • 運算子被明確寫出。 CONTAINS 會隨請求一起傳送,因此讀到 URL 的人知道 dune 是在尋找部分比對。在之前的版本中,這活在一個 if 裡面。
  • 名稱不是欄位的名稱。 authorName 不存在於任何文件中——作者在另一個集合中——但仍然可以像任何其他欄位一樣被過濾與排序。
  • 端點的簽名不會成長。 增加 copies 過濾器、日期範圍或第十個欄位,不會改變控制器的一行程式碼:它只會改變一個 enum 的一行。
  • 格式對每個清單都相同。 作者與借閱紀錄都以相同方式被請求,因此客戶端只需撰寫一個序列化器,而不是每個畫面一個。

這些都不是免費的:達到這個目標每個實體需要四個檔案,外加每個資料庫引擎一個轉換器,而且有些專案並不值得這麼做。本文剩下的部分將說明為什麼是這種形狀、它的成本以及何時不值得。



五個耦合點,以及一個不同種類的點

端點與呼叫它的人被綁在一起的地方有五個,而且它們並非同一種類。前四個透過閱讀檔案就能看見;第五個只有在第二個清單出現時才會變得可見。

1. 運算子活在方法主體內。 title 是用 $regex 解析,而 minCopies 是用 $gte,但兩個名稱都沒有說出來,這表示過濾器的行為可以在不碰簽名的情況下改變:把那個 $regex 改成精確比對不會破壞任何編譯,唯一的訊號是回應開始帶回更少的列。

2. 參數名稱就是欄位名稱。 sortBy=publishedAt 能運作是因為該字串被直接傳給 .sort()。在 schema 中重新命名屬性會留下兩條出路:破壞已經在流通的 URL,或是在控制器內保留一張從舊名稱到新名稱的別名表——這正是該模式最終會正式化的翻譯對映,只是寫得太晚而且只針對移動的那個欄位。

3. 簽名隨著欄位乘以運算子而成長。 minCopies 涵蓋了 copies 其中一種可能的比較;最大值是另一個參數,而精確範圍是第三個。端點累積的不是每個欄位一個參數,而是每個有人想對某欄位提出的問題一個參數。

4. 可以被過濾的東西沒有寫在任何地方:它是 if 的殘餘。 要知道端點接受什麼,你必須閱讀整個方法並記住分支。在排序方面甚至沒有分支可讀,因為 sortBy 直接進入 .sort():文件中的任何路徑都是有效的排序,包括清單不會回傳的那些欄位。

5. 格式對這個端點來說是私有的。 下一個清單——作者、借閱、複本——會從頭開始再次決定一切:頁面是用 page 還是 offset 請求,排序是用 sortBy 加上 order 還是單一的 sort=-publishedAt,布林值是用 true1 還是單純參數的存在來傳遞。在客戶端,每個畫面都寫自己的序列化器,而且沒有一個與前一個足夠相似到可以共用。

前四個是耦合的麻煩事:它們活在一個檔案內,透過編輯該檔案來修正,而修正它們的成本不取決於你等了多久。第五個是不同種類。它不活在任何檔案中,而是活在撰寫端點的人與消費它的人之間的協議中,而且它不會隨著欄位數量成長:它隨著清單數量乘以客戶端數量而成長。

只有單一清單、四個固定過濾器與一個畫面呼叫它時,這五個都沒有可觀察到的成本,而上面的方法是對問題的適當解答。它們在三個條件出現時變得可衡量,而且這三個條件往往一起出現:清單不再只有一個,客戶端不再只有一個,以及過濾器不再固定,因為使用者從表格標頭組合它們。



三種成本



1. URL 是 schema 的公開部分

在查詢字串中傳遞的名稱是文件欄位的名稱,而已發佈的 URL 沒有版本也沒有棄用:只要有人繼續使用它,它就存在。當 publishedAt 變成 firstPublishedAt 的那一天,編譯器或測試都不會說任何話,壞掉的是已經在儲存庫外部流通的連結。然而成本不是在重新命名時支付:它支付在你不會重新命名這個事實,因為既然沒有辦法知道誰用舊名稱呼叫,遷移就被延後,而不再描述它所儲存內容的名稱就保留下來。



2. 端點以相乘而非相加的方式成長

簽名累積每個可以對欄位提出的問題一個參數,而關於日期或數字的有用問題有好幾個;再乘以清單的數量,因為每個清單都從頭開始重複這個練習。效果體現在成長的方向:參數進來但不出去,因為移除 minCopies 需要證明沒有人呼叫它,而這個證明無法針對沒有在任何地方宣告的合約產生。該方法最終成為曾經呼叫過它的每個畫面的總和,包括那些已經不存在的。



3. 可以被要求的東西沒有寫在任何地方

sortBy 以文字到達並直接進入 .sort(),因此你可以排序的欄位清單不是由端點決定:它是由 schema 決定。而對欄位排序是一種讀取它的方式——用 sortBy=acquisitionPrice 與幾頁資料,你可以重建整個目錄中採購價格的相對順序,而回應從未回傳任何單一價格。二階效果是控制所在之處:該表面是透過編輯schema而變寬,而不是控制器,因此明天加入供應商利潤的人是在用一個沒有碰觸任何人會查看的檔案的 diff 來擴大 API 暴露的範圍。

這三種成本共享一個根源:客戶端可能要求的東西沒有以資料形式存在於任何地方,而是散落在方法簽名、幾個 if 的主體以及每個畫面組裝其 URL 的方式中。



值得捨棄的理由

Criteria 模式幾乎總是以相同的論點被引入:它避免了儲存庫方法的爆炸。在 CodelyTV 的表述中(這是西班牙語世界中該模式的參考),如果你必須依多個欄位過濾,「我們最終可能會有一個儲存庫,每個要過濾的欄位一個方法,加上可能存在的任何排列組合」——而 criteria 在尊重開放/封閉原則的同時解決了它。

它所描述的問題是真的,推理也是正確的。值得衡量的是它的大小。一個真實的儲存庫不會累積排列組合,它累積的是實際需要的那些方法:findByTitlefindByAuthorAndAvailable,以及不多不少的其他。成長不是組合式的,而是等於畫面的數量,而介面中四或五個相似的方法讀起來尷尬但修正起來便宜——它們正是上面清單中四個局部且可逆的點之一。

這個論點也沒有觸及某件事。方法爆炸完全在後端內部被解決:一個由 use case 手動組裝的 criteria,使用 new BookCriteria({ filters: [...] }),就已經移除了它,而為了做到這點,你不需要 DTO、不需要驗證、不需要公開欄位清單,也不需要在 URL 中傳遞運算子。一個只以此為理由的實作只會停在後端邊緣之前,而那正是本文問題開始的地方。另一個流傳的論點——這樣你就可以更換資料庫——在這裡更薄弱,因為 criteria 的轉換器正是遷移中便宜的部分;TypeORM 章節會展示它,但它是作為可驗證的後果而不是動機。

注意:可攜性論點確實有一個被認真捍衛的地方,那就是 Repository 模式,而我在那裡詳細衡量了它:NestJS 中的 Repository 模式——一個碰巧住在資料庫中的集合。如果你對那個討論感興趣,它全都在那裡;對接下來的内容來說,知道它不是 criteria 所支付的就足夠了。

取代它的問題是整個合約有哪些可用:不是為了避免介面中重複的方法,而是讓客戶端知道它可以要求什麼,而伺服器知道它接受什麼。在那裡,生態系提供的比通常被承認的還多。



已經被解決的事

nestjs-paginate 是開箱即用走得最遠的選擇,而且值得毫無保留地這麼說。目錄清單,在 15.0.1 版完整呈現:

// src/book/book.controller.ts
@Get()
async getAll(@Paginate() query: PaginateQuery): Promise<Paginated<Book>> {
  return paginate(query, this.repository, {
    relations: ["author"],
    sortableColumns: ["title", "publishedAt", "copies"],
    searchableColumns: ["title", "author.name"],
    filterableColumns: {
      available: [FilterOperator.EQ],
      copies: [FilterOperator.GTE, FilterOperator.LTE],
      "author.name": [FilterOperator.ILIKE],
    },
    defaultSortBy: [["publishedAt", "DESC"]],
    defaultLimit: 20,
    maxLimit: 100,
  });
}

Enter fullscreen mode

Exit fullscreen mode

GET /books?filter.available=$eq:true&filter.copies=$gte:3&sortBy=publishedAt:DESC&page=2&limit=20

Enter fullscreen mode

Exit fullscreen mode

sortableColumns 是必填欄位,不是可選的,而 filterableColumns 宣告每個欄位接受哪些運算子,出自一個包含十一個運算子的目錄——$eq$gte$in$btw$ilike$null$contains 及其同伴——加上 $not 後綴與 $all / $any / $none 量化器。maxLimit 設定頁面上限,searchableColumns 提供全文搜尋,有游標分頁,而且 filterExpressionMaxComplexity 限制過濾器表達式可擁有的節點數量,以免有人用巢狀 filter= 把伺服器搞垮。也要注意 "author.name" 能運作:名稱可以跨越關聯,因此即使是本文用作嚴苛測試的案例也被涵蓋了。成本 2 消失了——一個參數——成本 3 也消失了:沒有宣告的東西不會進來。

它沒有解決的是詞彙,而其他一切都由此而來。接受的名稱是 Column<T> 型別,這是實體的屬性路徑,因此 URL 中的 publishedAt 在類別中也是 publishedAt,成本 1 依然未被觸及。而 paginate() 接受 TypeORM 的 Repository<T>SelectQueryBuilder<T> 並回傳 TypeORM 實體:合約很優秀,但它與 ORM 密不可分,因此對使用 Mongoose 或 Prisma 的人來說它不存在,而且 use case 無法在不匯入 TypeORM 的情況下表達它。

GraphQL 透過另一條路徑解決整個問題——客戶端宣告它想要什麼,而 schema 就是合約:

query {
  books(where: { available: true }, orderBy: { publishedAt: DESC }, first: 20) {
    title
    author {
      name
    }
  }
}

Enter fullscreen mode

Exit fullscreen mode

三種成本一次消失。代價不是函式庫,而是傳輸:HTTP 快取、授權、速率限制與可觀測性全都移到其他地方,這讓它成為一個架構決策,而不是加到清單上的一層。

將整個 req.query 傳給 find() 在零行程式碼中解決了端點的成長:

@Get()
async getAll(@Query() query: FilterQuery<BookDocument>) {
  return this.model.find(query);
}

Enter fullscreen mode

Exit fullscreen mode

而它把另外兩種成本加劇到極致,因為公開詞彙變成引擎的全部:?acquisitionPrice[$gt]=0 會依一個沒有人決定要暴露的欄位過濾,而且端點的表面不再有任何人能說出的限制。

Specification 與 Query Object,這些經典模式,解決了領域內部條件的組合——以草圖形式,repository.match(new Available().and(new PublishedAfter(2020)))。這是一個真實的問題,也是前一節討論的問題,但兩者都沒有提到 HTTP 傳輸或驗證到達的內容,而在這裡這佔了一半的工作。

成本 1 · URL 與 schema 耦合 成本 2 · 端點成長 成本 3 · 意外表面
nestjs-paginate 未處理:公開名稱就是實體屬性 已解決:單一參數 已解決sortableColumns 是必填
GraphQL 已解決:schema 就是合約 已解決 已解決
ORM 的 where 傳入 find() 加劇它 已解決,沒有限制 加劇它:表面是整個引擎
Specification / Query Object 未處理 已解決 在後端內部 未處理

注意:表格沒有衡量人機工程或生產環境中第一個端點的時間,而且 nestjs-paginate 在兩者都大幅領先。它也沒有衡量在任何其他之前決定的條件:底下坐的是哪個持久化引擎。於 2026 年 8 月針對 nestjs-paginate 15.0.1 與 TypeORM 1.1.0 檢查;這些 API 會在主要版本中改變。

差距就在那裡。對任何不在 TypeORM 上的人來說,這些都不可用,而對在它上面的人來說,合約最終是以 ORM 的詞彙表達。缺少的是一個既不命名欄位也不命名函式庫的清單描述。



論點

Fowler 用一行定義了 Query Object——「一個代表資料庫查詢的物件」——並將其發展為一個直譯器:一個能夠將自己轉成 SQL 的物件結構。兩種表述都朝向引擎。本文的表述則是反過來看:

Criteria 不是在 URL 中旅行的查詢:它是清單的描述——什麼被過濾、如何排序、哪一頁——用領域的詞彙撰寫,並被翻譯兩次,在每個邊界各一次。

這句話決定了三個部分,而這三個部分佔據了文章剩下的部分。

每個實體一個公開欄位 enum。 合約的詞彙以資料形式存在於一個檔案中,而不是幾個 if 的殘餘。這是你決定客戶端可以命名什麼的地方,而這個決定不再取決於 schema 碰巧包含的東西:這是對成本 1 與 3 的直接回答。

一個沒有依賴的領域 criteria。 描述清單的物件不匯入 NestJS、不匯入驅動程式、也不匯入 ORM。Use case 建構它並交給儲存庫,而不知道背後是什麼,這允許同一個清單可以從 Mongo、從 Postgres 或從測試中的記憶體替身提供。

兩個彼此一無所知的翻譯。 第一個將 HTTP 請求轉成 criteria 並活在應用層;第二個將 criteria 轉成引擎的查詢並活在基礎設施中。兩者都不知道對方存在,而這就是接下來兩節要測試的特性:更換引擎只碰第二個,而改變客戶端可以要求的東西只碰第一個。



實作



領域 criteria

這是整個檔案,不是摘錄:

// src/shared/domain/criteria/criteria.ts
type Props = {
  filters?: CriteriaFilter[];
  order?: CriteriaOrder | null;
  page?: number | null;
  pageSize?: number | null;
  search?: string | null;
};

export abstract class Criteria<T extends string> {
  private _filters: CriteriaFilter[];
  private _order: CriteriaOrder | null;
  private _page: number | null;
  private _pageSize: number | null;
  private _search: string | null;

  constructor({ filters, order, page, pageSize, search }: Props = {}) {
    this._filters = filters ?? [];
    this._order = order ?? null;
    this._page = page ?? null;
    this._pageSize = pageSize ?? null;
    this._search = search ?? null;
  }

  get filters() {
    return this._filters;
  }

  get order() {
    return this._order;
  }

  get page() {
    return this._page;
  }

  get pageSize() {
    return this._pageSize;
  }

  get search() {
    return this._search;
  }

  // Replaces the whole list: this is what the mapper does with the request's filters.
  setFilters(v: CriteriaFilter[]) {
    this._filters = v;
  }

  // Accumulates: what the server imposes is added and the request cannot drop it.
  addFilters(v: CriteriaFilter[]) {
    this._filters = [...this._filters, ...v];
  }

  find(field: T): CriteriaFilter[] {
    return this._filters.filter((f) => f.field === field);
  }
}

Enter fullscreen mode

Exit fullscreen mode

這個檔案重要的是它不包含的東西:沒有裝飾器、沒有 NestJS 匯入、沒有資料庫驅動程式匯入。這個特性是可檢查的——在專案依賴未安裝的情況下它可以編譯——而其他一切都依賴於它:同一個物件可以在 use case 中建構、旅行到 Mongo 儲存庫,也可以在測試期間旅行到記憶體替身。

三個細節比看起來更有份量。T extends string 參數是將每個 criteria 綁定到其欄位清單的東西,因此如果名稱不在實體的 enum 中,criteria.find("subtitle") 就不會編譯。find 回傳清單而不是單一過濾器,因為一個欄位可以攜帶兩個——publishedAt 在一個日期之後且在另一個日期之前是一個區間——而翻譯者需要同時擁有兩者。setFiltersaddFilters 的區分存在是因為它們是兩種不同的情況:客戶端的過濾器取代清單,而伺服器強加的那些則累積,而且用不同的名稱時,差異在 use case 中是可見的,而不是必須被記住。



型別化的過濾器

過濾器是一個欄位、一個運算子與一些值。基礎類別固定前兩個,並將第三個留給每種型別:

// src/shared/domain/criteria/criteria-filter.ts
export abstract class CriteriaFilter {
  readonly field: string;
  readonly operator: CriteriaFilterOperator;

  constructor({ field, operator }: CriteriaFilterProps) {
    this.field = field;
    this.operator = operator;
  }

  abstract hasValues(): boolean;
}

Enter fullscreen mode

Exit fullscreen mode

// src/shared/domain/criteria/criteria-number-filter.ts
export class CriteriaNumberFilter extends CriteriaFilter {
  readonly values: number[];

  constructor(props: CriteriaFilterProps & { values: number[] }) {
    super(props);

    this.values = props.values;
  }

  numbers(): number[] {
    return this.values;
  }

  // A filter with no values must not restrict the query.
  hasValues(): boolean {
    return this.numbers().length > 0;
  }
}

Enter fullscreen mode

Exit fullscreen mode

有一種常見的替代方案,值得說明為什麼它比較差:儲存 values: unknown[] 並搭配一個判別欄位 type: "string" | "number" | "date" | "boolean"。使用那種形狀,轉換器會對 typeswitch,而編譯器不會檢查它讀取的值是否符合所在的分支,因此每個 case 都需要 as number[] 斷言。使用每個型別一個類別,filter instanceof CriteriaNumberFilter 會縮小型別,而 filter.numbers() 已經回傳 number[]。差異在基礎設施轉換器中被收取,那是模式中一個長長的 switch,涵蓋運算子,也是最容易無聲出錯的地方。



公開欄位 enum

這個檔案是客戶端可以命名的整個表面:

// src/book/domain/criteria/book-criteria-field.ts
export enum BookCriteriaField {
  ID = "id",
  TITLE = "title",
  AUTHOR_NAME = "authorName",
  PUBLISHED_AT = "publishedAt",
  COPIES = "copies",
  AVAILABLE = "available",
}

Enter fullscreen mode

Exit fullscreen mode

// src/book/domain/criteria/book-criteria.ts
export class BookCriteria extends Criteria<BookCriteriaField> {}

Enter fullscreen mode

Exit fullscreen mode

acquisitionPrice 不在裡面,而這個缺席就是對成本 3 的完整回答:API 的表面不再由 schema 決定。明天將供應商利潤加入文件的人不會擴大任何東西,因為從 URL 命名它將意味著編輯這個檔案,而這正是有人在審核時會尋找它的地方。

另一方面,authorName 在裡面,而它不是文件欄位。這個 enum 是清單的詞彙,而不是 schema 的詞彙——而這就是成本 1 被解決的地方,因為公開名稱不再與欄位名稱綁定,重新命名屬性變成內部變更。authorName 活在另一個集合中是轉換器的問題,而嚴苛測試會在後面處理它。

具體的 criteria 只有一行,因為它所有的型別都來自 enum:從這裡開始,任何在這六個名稱之外的 find 都不會編譯。



請求

這是模式中唯一帶有裝飾器的檔案,而這種集中是故意的:它是所有你無法控制的東西通過的邊界。

// src/shared/application/dto/criteria-request.ts
export class CriteriaFilterRequest {
  @IsString()
  @IsNotEmpty()
  field: string;

  @IsEnum(CriteriaFilterOperator)
  operator: CriteriaFilterOperator;

  // qs returns a string when the query carries `value=x` once, and an array when it
  // carries indexes (`value[0]=x`); normalised so it does not depend on how many
  // values the client happened to send.
  @IsDefined()
  @Transform(({ value }) => (Array.isArray(value) ? value : [value]))
  @IsArray()
  @IsString({ each: true })
  value: string[];
}

export class CriteriaRequest {
  @IsOptional()
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => CriteriaFilterRequest)
  filters?: CriteriaFilterRequest[];

  @IsOptional()
  @ValidateNested()
  @Type(() => CriteriaOrderRequest)
  order?: CriteriaOrderRequest;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  page?: number;

  @IsOptional()
  @Type(() => Number)
  @IsInt()
  @Min(1)
  pageSize?: number;

  @IsOptional()
  @IsString()
  search?: string;
}

Enter fullscreen mode

Exit fullscreen mode

兩個在應用程式啟動時的設定決定這是否運作,而兩個都會無聲失敗:

// src/main.ts
const app = await NestFactory.create<NestExpressApplication>(AppModule);

// Express 5 parses the query with `simple`, which is querystring.parse and does not
// nest: `order[by]` would arrive as a key literally called "order[by]".
app.set("query parser", "extended");

// Without `transform`, the DTO's @Type() decorators are not applied and `page` is
// still a string.
app.useGlobalPipes(new ValidationPipe({ transform: true }));

Enter fullscreen mode

Exit fullscreen mode

第一個是版本 5 中的變更:在 Express 5.2.1 的 lib/application.js 中,預設設定是 this.set('query parser', 'simple'),而 extended 是插入 qs 的東西。沒有它,filters[0][field]=title 不會以巢狀物件到達,而驗證會因為不是客戶端的錯而拒絕整個請求。



請求對映器

這是兩個翻譯中的第一個。它將 DTO 轉成 criteria,並沿途做了三個決定:

// src/shared/application/criteria/criteria-request-mapper.ts
export type CriteriaFilterOption<T extends string> = {
  field: T;
  type: CriteriaFilterType;
};

export abstract class CriteriaRequestMapper<T extends string> {
  abstract options(): CriteriaFilterOption<T>[];

  execute({ criteria, request }: Props<T>): Criteria<T> {
    if (request.search !== undefined) {
      criteria.setSearch(this.mapSearch(request.search));
    }

    // Pagination is always set, whether the request carries it or not: a criteria
    // with no pageSize translates into a query with no limit.
    criteria.setPage(this.mapPage(request.page));
    criteria.setPageSize(this.mapPageSize(request.pageSize));

    if (request.order !== undefined) {
      criteria.setOrder(
        new CriteriaOrder({
          orderBy: request.order.by,
          orderType: request.order.type,
        }),
      );
    }

    if (request.filters !== undefined) {
      const options = this.options();
      const result: CriteriaFilter[] = [];

      for (const filter of request.filters) {
        const mapped = this.mapFilter(filter, options);

        if (mapped !== null) {
          result.push(mapped);
        }
      }

      criteria.setFilters(result);
    }

    return criteria;
  }

  // The ceiling is what stops an absurd pageSize from ending up an unbounded find().
  private mapPageSize(value: number | undefined): number {
    if (value === undefined || !Number.isFinite(value)) {
      return DEFAULT_PAGE_SIZE;
    }

    return Math.min(Math.max(Math.trunc(value), 1), MAX_PAGE_SIZE);
  }

  // What is not in options() is not filtered: there is no field to apply it to.
  private mapFilter(
    filter: CriteriaFilterRequest,
    options: CriteriaFilterOption<T>[],
  ): CriteriaFilter | null {
    const option = options.find((o) => o.field === filter.field);

    if (option === undefined) {
      return null;
    }

    const props = { field: option.field, operator: filter.operator };

    switch (option.type) {
      case CriteriaFilterType.STRING:
        return new CriteriaStringFilter({ ...props, values: filter.value });

      case CriteriaFilterType.NUMBER:
        return new CriteriaNumberFilter({
          ...props,
          values: this.mapNumbers(filter.value),
        });

      // ...dates and booleans, the same way
    }
  }
}

Enter fullscreen mode

Exit fullscreen mode

而實體的那一個宣告清單,這是每個清單唯一必須撰寫的部分:

// src/book/application/criteria/book-criteria-request-mapper.ts
export class BookCriteriaRequestMapper extends CriteriaRequestMapper<BookCriteriaField> {
  options(): CriteriaFilterOption<BookCriteriaField>[] {
    return [
      { field: BookCriteriaField.ID, type: CriteriaFilterType.STRING },
      { field: BookCriteriaField.TITLE, type: CriteriaFilterType.STRING },
      { field: BookCriteriaField.AUTHOR_NAME, type: CriteriaFilterType.STRING },
      { field: BookCriteriaField.PUBLISHED_AT, type: CriteriaFilterType.DATE },
      { field: BookCriteriaField.COPIES, type: CriteriaFilterType.NUMBER },
      { field: BookCriteriaField.AVAILABLE, type: CriteriaFilterType.BOOLEAN },
    ];
  }
}

Enter fullscreen mode

Exit fullscreen mode

第一個決定是options() 中宣告的型別就是轉換文字的東西。查詢字串沒有型別,因此 copies"3" 會在這裡變成數字 3,一次,而不是在每個引擎轉換器中各自做一次。第二個是頁面上限:總是設定 pagepageSize,以 MAX_PAGE_SIZE 為上限,就是阻止客戶端把清單變成集合傾印的東西。

第三個值得帶著它的成本陳述。對未宣告欄位的過濾器會被無聲丟棄,它不會回傳 400,這表示客戶端的一個打字錯誤會產生未過濾的清單而不是一個可見的錯誤——這更難除錯。選擇這種方式的原因是幾個月前儲存的 URL 在某個欄位停止可過濾時,仍然會回傳某個合理的東西,而不是壞掉。nestjs-paginate 採取相反的決定並提供 throwOnInvalidFilter;兩者都有道理,而沒有選擇是沒有道理的。

注意:編譯器不會檢查 options() 是否涵蓋整個 enum。將其宣告為 Record<BookCriteriaField, CriteriaFilterType> 而不是清單會強制這點,但代價是失去表格形狀。目前的樣子,一個沒有型別的 enum 欄位是一個只有在實際過濾時才會注意到的疏忽。

這裡也是模式的成本,與好處在同一個地方:每個清單你必須撰寫四個檔案——enum、單行 criteria、帶有 options() 的對映器,以及稍後出現的基礎設施對映——而之前只有五個 @Query()。你換來的是這四個可以在一分鐘內讀完,並且說出關於端點接受什麼的全部真相。



Use case 與 port

領域儲存庫暴露單一的列出方法:

// src/book/domain/repository/book.repository.ts
export interface BookRepository {
  pagination(criteria

https://dev.to/chacaponquin/the-criteria-pattern-in-nestjs-what-a-client-may-ask-for-is-a-file-not-a-signature-3ifm

https://www.worldprogramming.org/posts/the-criteria-pattern-in-nestjs-what-a-client-may-ask-for-is-a-file-not-a-signature-2qbimp

[Submitted on 9 Jun 2025 (v1), last revised 19 Aug 2026 (this version, v4)]

View PDF
HTML (experimental)

Abstract:蛋白質生成模型在蛋白質設計方面展現了顯著的潛力,但其成功率仍受限於依賴人工整理的序列-結構資料集,以及監督式目標與實際設計目標之間的錯位。我們提出 ProteinZero,這是一個用於反向摺疊模型的線上強化學習框架,能夠實現可擴展、自動化且持續的自我改進,並提供計算效率高的回饋。ProteinZero 採用結合來自 ESMFold 的結構引導與新型自我衍生的 ddG 預測器的獎勵管線,在避免物理基礎方法高昂成本的同時提供穩定的多目標訊號。為了確保線上 RL 的穩健性,我們進一步引入一種新型的嵌入層級多樣性正則化器,可減輕模式崩潰並促進具功能意義的序列變異。在平衡多獎勵優化、來自參考模型的 KL 散度以及多樣性正則化的一般 RL 表述中,ProteinZero 在可設計性、穩定性、回復率與多樣性等方面均取得穩健的改進。在 CATH-4.3 基準測試中,它持續優於包含 ProteinMPNN、ESM-IF 與 InstructPLM 在內的現有最先進基準,將設計失敗率降低 36-48%,並在多樣摺疊中達到超過 90% 的成功率。重要的是,完整的 RL 運行可在單一 8 張 GPU 節點上於三天內執行完畢,包括獎勵計算與資料生成。這些結果顯示,高效的線上 RL 微調可透過讓蛋白質生成模型從自身輸出中持續演化並在無標記資料下優化多重設計目標,來補充監督式預訓練,為探索廣大的蛋白質設計空間開啟新的可能性。完整原始碼與模型檢查點將於發表後釋出。

Submission history

From: Jiajun Fan [view email]
[v1]
Mon, 9 Jun 2025 06:08:59 UTC (4,052 KB)
[v2]
Tue, 10 Jun 2025 18:30:51 UTC (4,052 KB)
[v3]
Mon, 2 Mar 2026 05:31:24 UTC (11,066 KB)
[v4]
Wed, 19 Aug 2026 22:42:25 UTC (11,067 KB)

https://www.worldprogramming.org/posts/proteinzero-self-improving-protein-generation-via-online-reinforcement-learning-cm4mfm


LLM 助理的記憶體架構品質標準封面圖

Aleksandr Kossarev



LLM 助理的記憶體架構品質標準

格式:稽核檢查清單 — 放在眼前逐項驗證。
版本: 1.1 (2026-08-21)
適用範圍: 任何基於 LLM 的助理與代理,具有長期記憶(對話、情節、語義、向量、圖譜、多模態),不限技術堆疊與平台。




1. 參考循環模型

稽核遵循一個通用的記憶循環。每個檢查清單項目對應此模型中的一個節點或邊緣。

INPUT (user, files, web, images, other agents, external models)
  → INGESTION (validation, meaning extraction, importance scoring)
  → STORAGE (messages, episodes, concepts, vectors, graphs, caches)
  → RETRIEVAL (relevance, freshness, importance, boundaries)
  → ASSEMBLY (formatting, compression, injection, budgets)
  → MODEL / LLM
  → OUTPUT (responses, actions: files, search, memory calls)
  → FEEDBACK (output returns to INGESTION)   ← loop is closed

Enter fullscreen mode

Exit fullscreen mode

關鍵特性是閉環:任何進入記憶的內容都會回到模型,並能自我複製。大多數嚴重的記憶體故障是邊緣故障,而非節點故障。




2. 如何執行稽核

  1. 從第 0 節(地圖)開始。 若沒有完整的儲存地圖,其他章節的結果將不可靠。
  2. 依序完成 A–K 各節。標記每個項目: yes / partial / no / n/a
  3. 對於每個 nopartial,記錄證據:檔案與行號、SQL 查詢與結果、傾印、提示快照、記錄項目。沒有證據的斷言不視為已驗證。
  4. 重要性標記:
    • [CRIT] — 直接造成資料遺失、污染、洩漏或記憶體不受控增長的風險。失敗 = 阻擋器。
    • [IMP] — 導致品質與可預測性 silently 退化的風險。
    • [REC] — 成熟度與可維護性。
  5. 只有在完成完整地圖後,才會給出判決(「稽核結果紀錄」章節)。



第 0 節. 系統地圖(準備)

  • [ ] 0.1 已編製完整的儲存地圖:關聯式資料庫、檔案與向量索引、JSON 儲存、快取(RAM 與磁碟)、外部資料來源。[CRIT]
  • [ ] 0.2 針對每個儲存體,識別寫入與讀取它的模組(讀寫所有權)。[CRIT]
  • [ ] 0.3 識別所有存取鍵:哪些欄位用於寫入、哪些用於檢索;確認寫入鍵與讀取鍵一致。[CRIT]
  • [ ] 0.4 識別模型輸出(回應、報告、動作結果)返回記憶體輸入的所有通道。[CRIT]
  • [ ] 0.5 驗證:被分析的程式碼與執行的程式碼一致(已檢查引入、確認使用中的實作,無「死」的平行版本存在)。[CRIT]
  • [ ] 0.6 擷取基準指標:儲存量(記錄數/位元組)、典型組裝後的上下文大小(token 數)、檢索時間。[IMP]
  • [ ] 0.7 識別使用者或外部內容進入提示中特權(系統)部分的全部位置。[CRIT]



A 節. 輸入驗證(INGESTION)

  • [ ] A1 所有外部來源(來自監控目錄的檔案、網路內容、圖片、其他代理的輸出)在寫入記憶體前都通過信任篩選。[CRIT]
  • [ ] A2 秘密(金鑰、權杖、密碼、個人資料)在索引與向量化前被偵測並排除。[CRIT]
  • [ ] A3 服務內容(日誌、診斷報告、服務標記、提示)被標記並從上下文組裝中排除(但可保留在歷史記錄中)。[CRIT]
  • [ ] A4 意義萃取不會將形式與意義混淆:程式碼片段、語法、路徑與技術標記不會成為「概念」或「記憶」。[IMP]
  • [ ] A5 每個記錄都記錄來源:來源、時間、撰寫代理、工作階段。[IMP]
  • [ ] A6 來自不受信任來源的記錄會被降低權重或置於獨立的信任區域。[IMP]
  • [ ] A7 多模態輸入中嵌入的內容(圖片中的文字、檔案後設資料)通過與明確文字相同的驗證。[CRIT]



B 節. 寫入冪等性與完整性(WRITE)

  • [ ] B1 寫入具冪等性:重複傳遞事件(重試、更新重複、雙重呼叫)不會產生重複 — 在應用層進行去重或在 schema 中使用 UNIQUE 約束。[CRIT]
  • [ ] B2 去重方向正確:保留目前記錄,抑制舊的重複項(而非相反)。[CRIT]
  • [ ] B3 去重依據語義/內容鍵,而不僅是「內容 + 來源」配對 — 可捕捉跨來源的重複。[IMP]
  • [ ] B4 寫入多個儲存體(訊息 + 向量 + 索引)具交易性或可補償:排除部分寫入(「訊息已存,向量未存」)。[IMP]
  • [ ] B5 寫入失敗不會無聲發生:必須記錄、計量、帶退避重試;無可觀測性的 silent return False 不可接受。[CRIT]
  • [ ] B6 壓縮表示(摘要)與原始內容一致寫入;空白或失敗的壓縮不會取代原始內容。[CRIT]



C 節. 成長管理(GROWTH)

  • [ ] C1 每個儲存體皆定義限制或保留原則(最大容量、歷史深度、溢位行為)。[IMP]
  • [ ] C2 重新索引/重建具冪等性:重複執行不會倍增記錄。[CRIT]
  • [ ] C3 已定義配額:單一記錄大小、每個工作階段/來源的記錄數、每個容器的總容量。[IMP]
  • [ ] C4 向量索引與來源同步:刪除或抑制記錄時會反映在索引中;已定義並強制執行操作順序(清除來源 → 重建索引)。[CRIT]
  • [ ] C5 快取(RAM 結構、摘要快取)具 TTL 或失效機制,並納入成長地圖。[IMP]
  • [ ] C6 監控容量動態;異常(單次操作成長一個數量級)會觸發警示。[IMP]
  • [ ] C7 多模態輸入以雜湊去重不會造成無界計數器成長,也不會允許以獨特變體串流「淹沒」記憶體新鮮度。[IMP]



D 節. 檢索排序與閘控(RETRIEVAL)

  • [ ] D1 記錄的靜態「重要性」不會補償低相關性:套用上下文自適應評分(重要性權重依查詢接近度調整),並使用軟相關性門檻,低於此門檻的記錄即使重要性高也不進入上下文。[CRIT]
  • [ ] D2 啟發式閘控(「對話式查詢 → 最小記憶體」)不會對合法查詢完全停用長期記憶;閘控條件必須狹窄且可稽核。[CRIT]
  • [ ] D3 重要性啟發式對通膨具抵抗力:使用者無法透過淹沒、問號、關鍵詞或其他明顯技巧提高記錄排名。[IMP]
  • [ ] D4 新鮮度提升受門檻限制:新但不相關的內容不會排擠舊但相關的內容。[IMP]
  • [ ] D5 跨重新啟動的檢索具決定性:鍵值具持久性;不使用非決定性的雜湊或識別碼作為存取鍵。[CRIT]
  • [ ] D6 門檻、限制與檢索權重已外部化至設定,而非硬編碼為魔術數字。[REC]
  • [ ] D7 外部驅動的重要性抑制(依外部內容封存、使用者回覆標記工作階段為「已解決」)受門檻、新鮮度保護與目前動作範圍限制。[CRIT]
  • [ ] D8 嵌入模型與資料的語言一致;當模型變更時,必須重新索引所有使用嵌入的儲存體。[CRIT]



E 節. 上下文組裝(ASSEMBLY)

  • [ ] E1 禁止盲目截斷:任何縮短都必須是語義摘要;不允許半句片段進入提示。[CRIT]
  • [ ] E2 完整原始內容與壓縮表示分開儲存(兩階段儲存:原始在歷史,壓縮在上下文)。[CRIT]
  • [ ] E3 壓縮表示快取在來源變更、還原或重新評估時失效;已定義 TTL。[IMP]
  • [ ] E4 結構化區塊(程式碼、表格)排除於壓縮之外或完整保留。[IMP]
  • [ ] E5 針對每個注入的區塊(包含工具結果與記憶)定義預算(字元/ token)。[IMP]
  • [ ] E6 使用者輸入未經所有欄位的嚴格驗證不得進入特權提示區塊(優先序、系統區段、記憶標頭)。[CRIT]
  • [ ] E7 可信任標記(系統前綴、工具標記、記憶來源標籤)無法被使用者文字模仿 — 輸入已針對其格式進行過濾。[CRIT]
  • [ ] E8 使用者查詢中的萬用字元(%_)與元字元在 LIKE/regex 記憶體搜尋中已跳脫。[IMP]
  • [ ] E9 從模型回應文字中萃取的指令/動作(檔案操作、搜尋、記憶呼叫)已驗證:路徑、權限、確認、限制。[CRIT]
  • [ ] E10 存在保護具情感意義記錄免於摘要與衰減的機制(受保護/活化記憶);受保護記錄的最大數量有上限。[REC]
  • [ ] E11 關鍵資訊置於上下文視窗的開頭與結尾,而非中間;對於長上下文(16K+ token),已考量位置注意力衰減效應(參:Liu et al., “Lost in the Middle,” 2023)。[IMP]



F 節. 回饋循環(FEEDBACK LOOP)

  • [ ] F1 模型輸出(回應、報告、動作結果)在返回記憶體前經過過濾:元內容(「對分析的分析」、服務摘要、關於上下文本身的報告)不會作為一般內容寫入。[CRIT]
  • [ ] F2 摘要器/壓縮器不會透過主要 LLM 用戶端閉環:呼叫為裸呼叫(無歷史、無寫入記憶)。[CRIT]
  • [ ] F3 摘要的摘要不可能發生(重複壓縮為無操作)。[IMP]
  • [ ] F4 存在遞迴 artifact 偵測器:元回應特徵、控制記憶體中模型生成內容的比例、成長警示。[IMP]
  • [ ] F5 插入回應文字的記憶召回結果,未經篩選不會「洩漏」回長期記憶。[CRIT]
  • [ ] F6 阻斷「輸入錯誤 → 扭曲回應 → 寫入扭曲至記憶」的路徑:上下文組裝錯誤不會被偽裝成有效內容。[CRIT]



G 節. 隔離與信任邊界(ISOLATION)

  • [ ] G1 隔離邊界不退化:實際上總是傳遞相同值(單一範圍識別碼)的篩選器不是邊界,而是邊界的模仿。[CRIT]
  • [ ] G2 跨範圍傳輸僅能透過明確受控通道(橋接、對映、允許關聯)進行,而非透過共享搜尋。[IMP]
  • [ ] G3 後備檢索分支不會返回不相關內容「只是為了返回東西」:空結果比隨機填充更誠實。[IMP]
  • [ ] G4 外部儲存體(屬於其他系統)依合約連接:外部端的 schema/語義變更會被偵測,而非 silently 吸收。[IMP]
  • [ ] G5 基於雜湊去重的首次寫入描述鎖存不是不可撤銷的:支援描述更新與重新感知時的重新描述。[IMP]
  • [ ] G6 領域/範圍分類對改述具穩健性;分類錯誤不會導致內容可見性不可恢復(存在恢復路徑)。[IMP]
  • [ ] G7 長期優先序受保護,不被暫時提升所抑制:已定義最大提升壽命與基礎權重恢復機制。[IMP]



H 節. 並行與遷移(CONCURRENCY)

  • [ ] H1 儲存存取統一(連線池 / 單一閘道);不存在帶有相互鎖定的多個獨立連線。[IMP]
  • [ ] H2 共享可變狀態(優先序、提升、快取、工作階段旗標)在非同步/多執行緒處理中受保護免於競爭。[CRIT]
  • [ ] H3 時間區間(衰減、新鮮度、TTL)從單調來源計算;系統時鐘變更不會破壞邏輯。[IMP]
  • [ ] H4 Schema 遷移具冪等性(變更前檢查是否存在),附帶備份與乾跑;排除或可偵測部分套用。[CRIT]
  • [ ] H5 測試與生產儲存體隔離:暫存/記憶體內資料庫、模擬外部 API,不與即時資料並行存取。[CRIT]
  • [ ] H6 單一事件的重複處理(系統輸入層的冪等鍵)排除雙重產生與雙重寫入。[IMP]



I 節. 可觀測性與恢復(OBSERVABILITY & RECOVERY)

  • [ ] I1 記憶污染監控已運作:模式特徵、品質分數、警示門檻。[IMP]
  • [ ] I2 排除無聲退化:每個優雅後備皆附帶指標/警示,而非僅一條日誌;大量以存根替換記憶必須可偵測。[CRIT]
  • [ ] I3 定期備份;還原程序已在實務中驗證(還原演練),而非僅有複本存在。[IMP]
  • [ ] I4 資料刪除為軟刪除:以權重抑制/封存取代 DELETE;已實作並測試恢復路徑(取消封存)。[IMP]
  • [ ] I5 存在健康檢查:用於活性驗證、儲存體間計數器一致性、外部依賴可用性的現成指令。[IMP]
  • [ ] I6 輔助基礎設施(網頁控制台、管理面板)的可用性受身份驗證保護;它們不會將記憶體暴露給外部。[CRIT]
  • [ ] I7 存在診斷對話協定 — 以結構化方式詢問代理其上下文狀態(「你看到什麼?」、「什麼造成干擾?」、「缺少什麼?」),作為外部指標的補充。[REC]
  • [ ] I8 適應性行為參數(人格特質、風格校準)在重新啟動後保持持久;每次啟動皆驗證恢復。[IMP]



J 節. 變更管理(CHANGE MANAGEMENT)

  • [ ] J1 每個觀察區間僅一個邏輯變更;排除同時獨立修改(否則回歸原因無法定位)。[CRIT]
  • [ ] J2 變更的成功標準可衡量:事前擷取基準(以 token/數字,而非「目測」),事後進行測量。[IMP]
  • [ ] J3 每個已解決的事件皆以回歸防護測試結案;沒有測試的修正視為不完整。[IMP]
  • [ ] J4 上下文組裝的本地驗證(「模型實際會看到什麼」的快照)先於線上測試進行。[IMP]
  • [ ] J5 變更可逆:每個 commit 一個變更,單一步驟的回滾不會拉動相鄰步驟。[IMP]
  • [ ] J6 維持文件循環:診斷 → 規格 → 審核 → 工作階段紀錄 → 報告 → 解決後模式(帶適用性訊號的泛化);在每個新規格開始時檢查模式的適用性。[REC]



K 節. 快速診斷(問題徵兆)

症狀 → 可能缺陷類別。用於完整檢查前的快速導航。

症狀 可能缺陷 參見章節
模型「分析」自己的報告/回應 回饋循環 F
技術文字、日誌、程式碼出現在「記憶」中 輸入驗證 / 評分 A, D
相同資料在上下文中重複 寫入冪等性 B
重建後儲存量大幅成長 非冪等索引 C
資料存在於儲存體中,但檢索結果為空 寫入/讀取鍵不符 0, D
「有時正常」、「重啟後才有效」 隱藏儲存、RAM 狀態、快取 0, C
資料庫「乾淨」但問題持續 地圖外存在儲存體 0
舊的不相關內容排擠新的 靜態重要性 vs 相關性 D
模型突然「失去」所有長期記憶 非決定性持久鍵 D
回應帶有不相關主題/代理的痕跡 隔離邊界突破 G
簡短隨意查詢得到空上下文 積極的檢索閘控 D
回應內容「斷在半個字」 組裝時盲目截斷 E
被發現的秘密出現在回應中 驗證前洩漏至記憶 A
負載下卡住,「資料庫已鎖定」 存取並行 H
代理「猜測」而非「記得」 嵌入模型不符 / 檢索路徑 0, D



稽核結果紀錄

完成完整檢查後填寫:

Audit date:             ______
System under audit:     ______
System map completed:   yes / no (if no — audit is not complete)

| Section | items | yes | partial | no | n/a | failed [CRIT] |
|---------|-------|-----|---------|----|-----|---------------|
| 0. Map               | 7  | | | | | |
| A. Input validation   | 7  | | | | | |
| B. Write integrity    | 6  | | | | | |
| C. Growth             | 7  | | | | | |
| D. Retrieval          | 8  | | | | | |
| E. Assembly           | 11 | | | | | |
| F. Feedback loop      | 6  | | | | | |
| G. Isolation          | 7  | | | | | |
| H. Concurrency        | 6  | | | | | |
| I. Observability       | 8  | | | | | |
| J. Change management  | 6  | | | | | |

Verdict:
- DOES NOT PASS: any [CRIT] failure — list: ______
- PASSES WITH CAVEATS: [CRIT] clean, [IMP] failures present: ______
- CONFORMS: no [CRIT]/[IMP] failures, only [REC] notes

Evidence (each failure → file/query/dump): ______
Priority remediation order: ______

Enter fullscreen mode

Exit fullscreen mode

判決規則:

  1. 第 0、A、B、E、F 節中任何 [CRIT] 失敗代表:目前形式的架構對長期記憶累積不安全 — 在修正前,資料將遺失、被污染或複製垃圾內容。
  2. C、D、G、H、I 節的 [CRIT] 失敗僅允許在有補償控制(監控、呼叫端限制)與修正計畫的情況下運作。
  3. 稽核在下一次重大架構變更前有效;每次變更後,需重新檢查受影響章節(架構世代變更時進行完整檢查)。



關於標準起源的說明

此標準是透過統整多年實際助理系統的運作經驗、事件與事後檢討而來,這些系統具備多層記憶:自我污染循環、非決定性鍵造成的無聲失憶、含有秘密的外部內容對長期儲存的污染、重複索引造成的容量倍增、嵌入模型語言不符導致的檢索失敗、同時變更造成的退化、重啟後人格特質遺失,以及將模型輸出與使用者輸入一同儲存造成的回饋循環。每個項目皆有對應類別的真實事件作為後盾;沒有事件基礎的項目標記為 [REC]。

作者:Aleksandr Kossarev, Jõgeva, Estonia
標籤:#ai #architecture #memory #standard

https://dev.to/aleksandr_kossarev_e23623/memory-architecture-quality-standard-for-llm-assistants-28b3

https://www.worldprogramming.org/posts/memory-architecture-quality-standard-for-llm-assistants-scadmk

外帶作業:公平地對 CognoDB 與其他幾個圖資料庫進行基準測試。公平規則很直接——每個資料庫都只能使用相同的極小資源上限:0.5 vCPU、256MB RAM,沒有例外。

說起來容易。有兩個平台沒能撐過去。

Memgraph 在匯入過程中不斷被 Linux OOM reaper 殺掉。一開始猜是缺少索引。錯了,檢查過、修正了,還是掛掉。第二次猜是儲存模式。Memgraph 的分析模式會跳過預寫日誌以加速匯入,聽起來很有希望。結果它也不支援基準測試所需的唯一性約束——你只能二選一,不能同時擁有。回到正常模式,乾淨地跑了一次,再跑一次確認。

又被殺掉了。設定正確的情況下兩次乾淨的失敗不是運氣不好。這是 Memgraph 的記憶體內交易引擎有個真正的記憶體底限,256MB 的機器無法通過。

ArangoDB 則是因為完全不同的原因失敗。它的文件深處提到:除非明確告訴它,否則它不會真的偵測 Docker 容器的記憶體限制。若放任不管,它會根據主機機器的 RAM 來調整內部快取,而不是容器的。設定了覆寫後,有進展,但還是掛了。

到了這個階段,誠實的選擇是:放寬上限直到所有東西都能塞進去(這會完全失去測試的意義),或者交付少於要求的資料庫數量。兩個都不對,所以專案中途加入了第五個平台——Kùzu,一個嵌入式圖資料庫,它直接在你自己的行程內執行,而不是作為獨立的伺服器。在你載入任何一列資料前,不會有閒置的常駐程式。

使用它時的峰值記憶體:1.94MB。在 256MB 中。而 Memgraph 和 ArangoDB 試圖存放相同資料時,就在這個數字上掛掉。

最終的實際結果比「本地端獲勝」更有趣。Kùzu 的「索引」查詢根本不是索引——它是一次完整掃描,設計上就不支援傳統索引。它還是比 AuraDB 真正的索引查詢快了大約 40 倍(4.7ms 對 196.7ms)。這不是更聰明的查詢引擎,而是直接測量出雲端資料庫的延遲有多少只是網路來回,而不是實際的工作量。

FalkorDB 和 Kùzu 的表現也沒有乾淨地分出高下。FalkorDB 贏了所有遍歷查詢,但在聚合上輸得很慘——在那個項目上比真正的雲端資料庫還慢。不同的引擎、不同的優勢,沒有單一贏家。

這些結果都沒有變成乾淨的五個綠勾勾表格,而這正是重點。如果第一次 Memgraph 嘗試就成功了,這些事情永遠不會浮上檯面。


完整方法論、每一次失敗、每一個數字:repo link

https://dev.to/sun_eater/two-of-our-six-graph-databases-died-under-the-exact-same-load-heres-why-thats-a-good-thing-17ap

https://www.worldprogramming.org/posts/two-of-our-six-graph-databases-died-under-the-exact-same-load-heres-why-thats-a-good-thing-asbk9p

framework-12-featured

4

Published Aug 20, 2026, 11:05 PM EDT

Simon 是電腦科學學士畢業生,從 2014 年開始撰寫科技相關文章,從 Windows 3.1 時代就開始使用 Windows 機器。在一家獨立遊戲工作室工作並擔任家族電腦問題的技術支援後,他找到了寫作的熱情,並決定運用自己的技能撰寫所有科技相關內容。

自從開始寫作生涯以來,他為許多不同刊物撰稿,例如 WorldStartListverse,以及 MakeTechEasier。然而,在 2019 年 2 月找到 MakeUseOf 這個家後,他最終轉移到其姊妹網站 XDA,為讀者帶來 Windows、Linux 和 DIY 電子產品的最新資訊。


Sign in to your XDA account

Summary

  • Fedora 預購 Framework 新款 Laptop 12 的銷售量超越 Windows 超過 10:1 — 對 Linux 而言是一大勝利。
  • Framework 的 Fedora Laptop 12 是他們價格最低的預組機,售價 699 美元,既親民又原生支援 Linux。
  • Framework 的 DIY、可維修設計與喜愛自訂化的 Linux 愛好者天生契合。

作為一名 Linux 粉絲,我總是喜歡慶祝它在對抗大公司時獲得勝利。然而,我從未在最瘋狂的夢想中想像過,一款 Linux 筆電的銷售量會超越其 Windows 版本超過十比一。幸運的是,我不用再做夢,因為 Framework 已確認其新款 Laptop 12 的 Fedora 預購量目前遠遠超越 Windows 版本,但如果你仔細想想,這其實非常合理。

Framework 的 Laptop 12 的 Fedora 銷售量是 Windows 的 10 倍以上

結果發現,喜愛可自訂化筆電的粉絲也喜歡可自訂化的作業系統;誰想得到呢?

幾天前,Framework 宣布新款 Laptop 12 的預購已經開放。這款新筆電搭載 Intel Core Series 3 處理器、Thunderbolt 4、Wi-Fi 7 連線,並可選配指紋辨識器和背光鍵盤。Framework 表示他們也與 Fedora 和 KDE 密切合作,為用戶提供基於 Linux 的 Laptop 12 版本。

嗯,看來初步銷售統計數據已經出爐,而對 Tux 團隊來說情況相當樂觀。在一則 X 貼文中,該公司確認 Fedora 版本的銷售量以超過 10 比 1 的比例超越 Windows 版本。

Framework 很快宣稱這是「Linux 桌機年」,但如果你深入了解實際情況,消費者選擇 Fedora 版本似乎是非常簡單的決定。首先,Framework 稱 Laptop 12 的 Fedora 版本是他們「有史以來價格最低的預組機」,售價只要 699 美元。在硬體危機的當下,價格親民的選擇最吸引人,尤其是如果有人已經擁有 Windows 授權,不想再多付一份錢。

此外,Framework 筆電的核心理念就是可自訂化、DIY 和自行維修。這些價值觀在 Linux 中都能找到,因此喜愛擺弄硬體的人,自然也會對他們的軟體抱持相同態度。無論如何,我還是希望真正的原因是人們現在就是越來越喜歡 Fedora 勝過 Windows。

https://www.xda-developers.com/frameworks-laptop-12-preorders-see-fedora-outselling-windows-by-over-10-to-1-and-it-makes-a-lot-of-sense/

https://www.worldprogramming.org/posts/frameworks-laptop-12-preorders-see-fedora-outselling-windows-by-over-10-to-1-and-it-makes-a-lot-of-sense-nfmkon


Google Gemini Live 將語音啟動的深度研究引入行動多工處理的封面圖片

Ali Farhat

Google 已將 Gemini Live 與其 Deep Research 功能連結,讓使用者能夠透過語音開始多步驟的研究任務,將其留在背景執行,待工作完成後再以語音或文字記錄的形式進行後續討論。這項改變將 Deep Research 從主要以提示引導的活動,轉變為更具對話性的行動工作流程,特別適合那些需要在不留在應用程式內的情況下捕捉研究請求的使用者。

關鍵區別不僅僅是語音輸入。Gemini Live 可以啟動一項研究流程,讓使用者在切換應用程式或鎖定手機時繼續進行。Google 將這種體驗描述為透過語音進行研究,當任務完成時會發出通知,並提供無縫回到對話的路徑。該公司的 Pixel 的 Gemini Deep Research 概覽 將此功能呈現為讓深度研究在行動裝置上更易使用的一部分。

Deep Research 本身旨在提供的遠不止單一回應。Google 已記錄了一個工作流程,其中 Gemini 會制定研究計畫、跨來源搜尋、視需要擴大調查,並產生包含來源連結的結構化報告。報告也可以匯出至 Google Docs。將此流程帶入 Gemini Live 改變了請求的開始方式以及使用者恢復的方式,而不是改變 Deep Research 的既定目的。



Gemini Live 研究工作流程有何改變

這次更新結合了對話式啟動與非同步執行。使用者可以大聲說明複雜主題,要求 Gemini Live 開始 Deep Research,然後在系統運作時轉向其他任務。當報告準備好時,使用者會收到通知,並可透過語音繼續討論或檢視文字記錄。

工作流程元素 已記錄的 Deep Research 體驗 Gemini Live 整合
開始請求 研究請求可以產生結構化計畫。 使用者可以透過與 Gemini Live 對話來啟動 Deep Research。
研究過程 Gemini 可以搜尋來源、擴大搜尋,並彙整報告。 研究可以在使用者切換任務或鎖定手機時繼續進行。
結果 結構化且富含引用的報告可以包含來源連結,並匯出至 Docs。 完成時可以觸發通知,後續進行語音討論或文字記錄檢視。

這種方法在任務需要時間但初始指示不需要時間時最為有用。準備會議、調查不熟悉的主題或細化問題的人,可以在想法出現時直接陳述目標,而不必立即撰寫詳細提示。其價值在於能夠交出多步驟任務,並在執行期間收回注意力。

這可以讓 AI 輔助研究 更自然地融入行動工作,因為中斷和情境切換在行動工作中很常見。

語音也改變了交付後的互動方式。Gemini Live 不再將報告視為最終成品,而是將其定位為持續對話的材料。這與 Google 將 Deep Research 描述為可精煉的迭代過程一致。使用者可以在檢視回傳內容後,探索發現、要求澄清或改變研究方向。



此改變對企業 AI 使用為何重要

對組織而言,非同步研究是 AI 工具中的重要模式。它將定義問題的行為與調查所需的時間分開。這可以讓 AI 輔助研究更自然地融入行動工作,因為中斷和情境切換在行動工作中很常見。

然而,不應將宣布的工作流程誤認為是 企業研究治理解決方案。提供的 Google 資料描述了研究規劃、來源連結報告、持續精煉以及 Docs 匯出。它們並未建立組織特定的資料處理、保留、核准流程或政策執行的控制。企業在評估將 Gemini Live 用於工作研究時,因此應區分語音引導任務建立的便利性與自身處理敏感資訊和驗證輸出的需求。

同樣的限制也適用於可用性和成本。提供的資料確認了此功能,但未提供完整的定價模式、資格矩陣、區域推出時程或語音啟動 Deep Research 的開發者 API 細節。這些問題對評估部署的團隊仍具相關性,但無法從現有的公告資料中獲得解答。

對開發者而言,立即的重要性主要在體驗層面,而非已宣布的平台介面。Google 已確認使用者導向的流程,可在語音對話、背景工作、通知和報告檢視之間切換。在提供的資料中,它並未宣布對應的 API、SDK 或整合控制。產品團隊應避免假設 Gemini Live 互動會自動作為可嵌入的研究工作流程提供。

Google 的舉措也反映了更廣泛的產品方向:研究輔助正逐漸不再綁定於單一聊天工作階段。有意義的比較不是競爭平台的清單或未經支援的功能聲稱。而是在對話中等待答案與 委派有界限的研究流程(可在使用者進行其他事情時完成)之間的差異。Google 的實作將語音作為該委派模型的入口點。

對企業團隊而言,語音啟動的研究可以引入新的途徑,讓工作問題進入 AI 系統。Scalevise 可以協助評估該途徑在何處創造生產力提升、何處需要人工審核,以及 AI 研究如何符合現有的治理實務。 我們的 AI 顧問團隊 可以將新興的助理功能轉化為符合您工作流程和風險需求的實際採用計畫。請求諮詢以評估您的 AI 研究工作流程。



常見問題

什麼是 Gemini Live Deep Research?

Gemini Live Deep Research 讓使用者可以透過語音向 Gemini Live 提出要求,開始多步驟的 Deep Research 任務,然後在完成後回來討論或檢視結果。

Gemini Live Deep Research 是否可以在手機鎖定時執行?

可以。Google 表示研究可以在使用者切換任務或鎖定手機時於背景繼續進行,並在完成時發出通知。

Gemini Deep Research 會產生什麼?

Google 將 Deep Research 描述為建立研究計畫、跨來源搜尋與擴大,並回傳包含來源連結的結構化報告。報告可以匯出至 Google Docs。

此公告是否確認企業治理控制或開發者 API?

否。提供的資料確認了使用者導向的研究工作流程,但未指定企業資料治理控制、定價細節或開發者 API。




結論

Google 的 Gemini Live 整合讓 Deep Research 在行動工作日中更容易啟動和重新檢視。其重要性在於結合語音請求、背景執行,以及圍繞已記錄研究流程的對話式後續討論。對組織而言,機會在於更流暢地存取 AI 輔助研究,而治理、推出和整合問題仍需另行評估。

https://dev.to/alifar/google-gemini-live-brings-voice-started-deep-research-to-mobile-multitasking-54ll

https://www.worldprogramming.org/posts/google-gemini-live-brings-voice-started-deep-research-to-mobile-multitasking-mvxslv

隨著 AI 模型變得更強大,這些模型被誤用的潛在風險也隨之增加——呼籲建立安全護欄以防止此類濫用的聲浪也日益高漲。AI 公司現在必須在尊重企業客戶隱私與監控使用情況以防可能問題之間,維持微妙的平衡。

察覺到有機會超越競爭對手 Anthropic,OpenAI 剛剛宣布了一項以隱私為中心的監控誤用安全方法。該公司正在向特定客戶預覽一項名為 Private Safety Processing 的新服務。這是一套自動化系統,能在監控潛在濫用的同時,完全不保留客戶的任何資料。

這套系統明顯與 Anthropic 最近宣布的資料保留政策背道而馳。該政策已引起部分客戶不滿,它允許這家 AI 實驗室在「涵蓋模型」的情況下,保留使用者資料(所有對話紀錄及其中的對話內容)長達 30 天。該公司表示,這些模型包括所有 Mythos 類別模型以及「具有類似能力的未來模型」。

這項在七月宣布的政策,目的是為了安全考量,讓實驗室得以篩選和分析潛在的不當行為。然而,它已深深引起某些處理大量敏感資料企業的擔憂,這些企業不希望資料被 AI 實驗室保存(或檢查)。

OpenAI——如同大多數其他 AI 公司——已透過遵守名為 Zero Data Retention 的政策,為客戶提供相對程度的隱私。ZDR 利用 OpenAI API 內的代理程式,以每個工作階段為單位監控濫用行為。透過這種方式,公司不會保留客戶資料,但仍能掃描不良活動而無需人工介入。值得注意的是,Anthropic 也大致遵守 ZDR——但「涵蓋模型」如 Fable 則屬例外。

OpenAI 表示,Private Safety Processing 是一項新技術,能擴大 ZDR 的適用範圍。它將其描述為一種長期安全監控形式,能評估多個對話的輸入與輸出——而非僅限單一對話。同樣地,監控由代理程式執行,若被觸發,便會捕捉互動並跨工作階段分析潛在誤用的跡象。

該新技術有助於 OpenAI 偵測跨越多個工作階段的惡意 AI 使用,一位發言人告訴 TechCrunch。惡意行為者——假設是試圖為網路攻擊開發惡意軟體的人——可能會分散他們的請求以避免被偵測。Private Safety Processing 能在無需人工審查使用者對話的情況下,分析這些多個對話以找出濫用跡象。

在系統被觸發的情況下,它可能會向 OpenAI 發送一個「明確定義的訊號」,警告特定類型的活動,公司表示。根據該訊號,OpenAI 便能決定是否「需要執行執法措施」,它說。若是如此,OpenAI 將聯繫客戶以取得更多脈絡,或與他們合作解決問題,而客戶可自行決定是否與 OpenAI 分享資料,發言人表示。

相較之下,Anthropic 表示對客戶資料的人工審查可能會發生,但僅限「透過受控存取路徑」,且涉及「一小群經過批准的審查者」。公司表示,每一次審查工作階段都會「記錄在防篡改的日誌中,審查者無法抑制或修改」。

OpenAI 與 Anthropic 之間的企業競爭目前相當緊張,雙方都在尋找任何能取得優勢的機會。一份近期報告顯示,OpenAI 第二季的成長速度慢於 Anthropic。Anthropic 的年化收入運行率據報現已達到 650 億美元。Anthropic 的投資者表示,它可能以 2 兆美元的估值 IPO,而 OpenAI 也正在準備其 IPO。

當您透過我們文章中的連結購買時,我們可能會賺取少量佣金。這不會影響我們的編輯獨立性。

Lucas 是 TechCrunch 的資深作家,負責報導人工智慧、消費性科技和新創公司。他之前曾在 Gizmodo 報導 AI 和網路安全。

您可以透過 [email protected] 聯繫 Lucas。

View Bio

https://techcrunch.com/2026/08/19/openai-seeks-to-one-up-anthropic-with-new-customer-privacy-protections/

https://www.worldprogramming.org/posts/openai-seeks-to-one-up-anthropic-with-new-customer-privacy-protections-aje7ho



VIDRAFT 如何在 Gemma-4 上達到 510.58 TPS:「The First Gemma Challenge」冠軍深度解析

TL;DR: VIDRAFT 在「The First Gemma Challenge」排行榜上以單一 NVIDIA A10G GPU 在 google/gemma-4-E4B-it 模型上取得經驗證的 510.58 tokens-per-second (TPS) 成績,同時擊敗了一個原始速度更快但未通過品質門檻的競爭對手。本文剖析使其成功的公開設定選擇,以及工程師們能在自己的推論調校工作中借鏡的技巧。




這是什麼

「The First Gemma Challenge」是一場受嚴格限制的推論速度競賽,有兩項硬性規則:固定使用單一 GPU(NVIDIA A10G)與固定模型(google/gemma-4-E4B-it)。參賽者無法更換更強的硬體或更輕量的模型,唯一能操作的槓桿就是軟體層級的最佳化

評分指標為TPS(Tokens Per Second,每秒生成 token 數),但同時設有Perplexity(PPL)預算——PPL 超過約 2.42 的提交將被取消資格,無論速度多快。主辦單位還針對參賽者從未見過的保留提示集進行盲測重新評估,這意味著任何過度擬合自報基準的設定都會被抓出來。

VIDRAFT 的優勝提交——設定名稱為 vidraft-fw188-ctk49-n64-patchbridge-v1——的成績如下:

  • TPS: 510.58
  • PPL: 2.3930
  • 狀態: 通過盲測重新評估 ✅

另一個競爭提交雖然錄得 535.91 TPS,但其 PPL 約為 2.44,超過品質門檻。因此這個較低的原始數值被認定為經驗證的 SOTA。




運作原理

優勝設定的公開 manifest.json 揭示了三個概念性的最佳化支柱:



1. 滑動視窗注意力壓縮(SLIDING_WINDOW=188

KV-cache 記憶體頻寬是自迴歸生成過程中的主要瓶頸。將注意力視窗限制在最近的 token 上能減輕這項壓力並提升吞吐量——但視窗縮得太小就會喪失上下文,導致 PPL 急遽上升。188 這個數值顯然不是整數,這強烈暗示它是透過實證調整而非直接採用預設值。團隊透過 HF_OVERRIDES 覆寫了模型的 text_config.sliding_window,並啟用 Flash Attention 滑動功能(FA_SLIDING=1)來配合。



2. 質心 Top-k 核心調校(CENTROID_TOP_K=49

此參數更接近核心層級,會同時影響吞吐量與 PPL。根據原始碼分析,團隊依序測試了 44、48、49 等數值——目標是找出在 PPL 仍在預算內的前提下所能使用的最大值。越大並不一定越好,這是在品質限制下的 Pareto 搜尋。



3. 暖機紀律——將初始化過程排除在測量視窗之外

該設定使用了:WARMUP_BRIDGE=1WARMUP_NUM_PROMPTS=64WARMUP_MAX_TOKENS=1WARMUP_SEED=42。這會在計時基準測試開始前先執行 64 個單一 token 的虛擬提示,讓 CUDA graph capture 與 JIT 編譯的成本在計時開始前就被吸收。原始文章指出這個暖機步驟大約貢獻了 15 TPS——在一個以數十 TPS 決勝負的競賽中,這是相當可觀的差距。

同樣重要的是:PRECACHE_BENCH=0 被明確設定,停用了會讓自報 TPS 虛增的旗標。團隊選擇測量盲測評估器實際會看到的真實表現。



其他值得注意的參數(已公開)

  • 推測解碼: 透過 SPECULATIVE_CONFIG 啟用,設定 num_speculative_tokens=7method=mtp——這是一種先草稿再驗證的方法,能增加每次正向傳遞所生成的 token 數
  • MAX_MODEL_LEN=4096
  • GPU_MEMORY_UTILIZATION=0.90
  • MAX_NUM_BATCHED_TOKENS=512
  • MAX_NUM_SEQS=1



基準測試與結果

提交 TPS PPL 盲測
VIDRAFT(vidraft-fw188-ctk49-n64-patchbridge-v1 510.58 2.3930 ✅ 通過
競爭提交 535.91 ~2.44 ❌ 未通過(PPL > 2.42)

最重要的啟示:原始吞吐量排名與驗證後排名出現分歧,因為品質門檻是根據保留提示分佈來執行的,而非參賽者自己的測試集。




如何嘗試

競賽中所使用的模型已由 Google 在 Hugging Face 公開提供:

huggingface-cli download google/gemma-4-E4B-it

Enter fullscreen mode

Exit fullscreen mode

特定的 VIDRAFT 設定(vidraft-fw188-ctk49-n64-patchbridge-v1)以及任何 VIDRAFT 專屬工具在本文撰寫時尚未確認已公開釋出。請查看 VIDRAFT 的 Hugging Face 組織 與他們的 GitHub 以取得最新資訊。如果開放取得管道,會優先在那裡公布。




常見問題

Q:在「速度」競賽中,為什麼 PPL 門檻比原始 TPS 更重要?
A:因為沒有品質底線的 TPS 很容易被操縱——你可以讓模型輸出垃圾內容來快速生成。PPL 上限加上盲測重新評估,共同確保速度數字反映的是真實、可部署的推論品質。

Q:我能將這些技術應用在其他模型或 GPU 上嗎?
A:概念——品質門控的參數搜尋、暖機分離、滑動視窗調校、推測解碼——都是通用的推論工程實務。特定數值(SLIDING_WINDOW=188CENTROID_TOP_K=49 等)是針對單一 A10G 上的 google/gemma-4-E4B-it 所調校的,應該視為其他硬體或模型設定的起點,而非直接複製貼上的目標。

Q:推測解碼(method=mtp)在這裡的作用是什麼?
A:一個更小、更快的「草稿」模型會先預測主模型接下來的幾個 token。主模型再在單一次正向傳遞中驗證這些預測。如果預測被接受,你就能在每個步驟中實際生成多個 token——在不改變模型權重或降低輸出品質的情況下提升測得的 TPS。


原文由 note(日本)於 2026-08-15 報導 — 原始文章

https://dev.to/ai_openfree_b23025ef075cf/how-vidraft-hit-51058-tps-on-gemma-4-a-deep-dive-into-the-first-gemma-challenge-win-15j6

https://www.worldprogramming.org/posts/how-vidraft-hit-51058-tps-on-gemma-4-a-deep-dive-into-the-first-gemma-challenge-win-z3jp0h


Cover image for repomapper v0.1.0: 任何儲存庫的 AGENTS.md 操作指南

Fenix



repomapper v0.1.0: 任何儲存庫的 AGENTS.md 操作指南

為任何程式碼儲存庫產生一份精簡的操作指南,格式為 AGENTS.md:包含子系統、測試、慣例與歷史陷阱。82 項測試。基於論文 Probe-and-Refine Tuning of Repository Guidance for Coding Agents (2026)



問題所在

程式碼代理需要儲存庫的操作知識,而這些知識並未存在於程式碼中:

  • 哪些檔案存放各個子系統。
  • 如何執行測試套件。
  • 歷史上哪些工作流程曾導致錯誤的修正。
  • 專案的慣例與結構。

人類會維護 AGENTS.md 檔案來提供這些上下文,但手動建立非常耗時且容易過時。



解決方案

RepoMapper 以三個步驟自動化此流程:

  1. 掃描:儲存庫結構、語言、進入點、測試、子系統、依賴與慣例。
  2. 探測:合成任務(import 檢查、語法檢查、測試執行器、設定檔驗證)。
  3. 產生:以 AGENTS.md 格式輸出精簡的操作指南(<3000 字元)。



基本用法

git clone https://github.com/amurlaniakea/repomapper.git
cd repomapper
python3 -m venv venv && source venv/bin/activate && pip install -e .

# 為某個儲存庫產生 AGENTS.md
python3 -m repomapper /path/to/repo

Enter fullscreen mode

Exit fullscreen mode



相關連結

你的代理每次遇到新儲存庫時,要花多少時間學習那些早已被寫好的知識?

https://dev.to/magopredator/repomapper-v010-guia-operativa-agentsmd-para-cualquier-repositorio-d09

https://www.worldprogramming.org/posts/repomapper-v010-guia-operativa-agentsmd-para-cualquier-repositorio-qo4hnh

Juan Barriteau

Cipr(Cosmic Index of Public Resources)是一個去中心化、分散式且具抗審查能力的網路索引,由網域擁有者自行掌控自己的條目。

沒有爬蟲決定你是否值得被索引。沒有策展人審核你的提交。沒有中央權威可以將你除名。如果你擁有一個網域,你只需發布一個小型 daemon、加入一筆 DNS TXT 記錄,你的網站就會在幾分鐘內出現在全球索引中。更新條目或永久離開也非常快速且容易。

Cipr 並非傳統意義上的搜尋引擎。它是一個共享的點對點目錄,每位參與者都擁有一份完整副本,並透過病毒式傳播保持同步。搜尋結果依據標準化、可公開稽核的因素(基於擁有者宣告之元資料的 BM25)進行排名,而非不透明的演算法或廣告收益。

審查 Cipr 條目需要 DNS 等級的介入——這與讓一個網域下架所需的基礎設施層級動作相同。沒有中央伺服器可以關閉,沒有 API 金鑰可以撤銷,也沒有服務條款可以違反。

它最適合小型網路:個人部落格、家庭實驗室服務、獨立專案、社群資源——這些主流搜尋引擎越來越常埋沒或完全忽略的網站類型。

Ciprnode zero 是 Cipr 通訊協定的第一個也是參考實作。它完全基於 Deno 建構,沒有任何 runtime npm 依賴。本地索引使用 SQLite 資料庫,搭配外部內容的 FTS5 虛擬表格與 BM25 排名。API 採用嚴格的語義 RESTful 實作,透過 HAL+JSON 完全符合 HATEOAS 規範,並實際使用 QUERY HTTP 方法(draft-ietf-httpbis-safe-method-with-body)。

內建的網頁介面(ciprface)提供搜尋功能,可依語言、地理鄰近度、冒犯程度與時間戳記進行篩選。驗證採用 DNS TXT 三重驗證(透過自訂 TLS 連線使用 3 個隨機 DoH 解析器)與病毒式 P2P 傳播。沒有中央伺服器、沒有區塊鏈、沒有代幣。採用 MIT 授權、可自行架設,能編譯成適用於 Linux、Windows 和 macOS 的獨立執行檔。

更多資訊:https://cipr.info

目前有三個節點正在運行:

https://dev.to/barriteau/cipr-and-ciprnode-zero-1b89

https://www.worldprogramming.org/posts/cipr-and-ciprnode-zero-xozdup