Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
50609db
feat(evaluation): complete end-to-end benchmark pipeline
dzx0902 Jun 6, 2026
cc37c58
fix(evaluation): harden live benchmark scoring and embedding retries
dzx0902 Jun 7, 2026
767eb29
feat(evaluation): add official benchmarks and semantic judging
dzx0902 Jun 8, 2026
231474c
feat(memory): add batch writes and explicit supersession
dzx0902 Jun 8, 2026
db7c101
docs: gap 9 re-audit pass — align deliverables with HEAD 2dd0d64
hopecommon Jun 8, 2026
ddf1d93
docs(gap5): add ER and sequence diagrams (mermaid + SVG)
hopecommon Jun 8, 2026
8883161
docs(gap5): shorten 02-er-full labels to reduce overlap at MEMORY_ITEM
hopecommon Jun 8, 2026
055f89c
docs(gap5): codex review pass — correct API paths, triggers, ER edges
hopecommon Jun 8, 2026
9c2594d
docs(gap2): add research landscape analysis
hopecommon Jun 8, 2026
b70f789
docs(gap3): expand innovation analysis for final report
hopecommon Jun 8, 2026
072c376
docs(gap8): map annotated source and SQL appendix
hopecommon Jun 8, 2026
02e51e8
docs(gap4): add screenshot and evidence inventory
hopecommon Jun 9, 2026
1302a5e
docs(gap4): include raw command logs as PNG renderings' source of truth
hopecommon Jun 9, 2026
6620abf
docs(gap6): contribution ledger with per-member commit audits
hopecommon Jun 9, 2026
965ea4a
feat(evaluation): add resumable benchmark judging
dzx0902 Jun 9, 2026
a7e5aae
Merge branch 'dev' into feat/evaluation-benchmark-framework
dzx0902 Jun 9, 2026
525c4d8
fix(test): avoid unused datetime imports in PR build
dzx0902 Jun 9, 2026
8edbc00
Merge pull request #72 from dzx0902/feat/evaluation-benchmark-framework
dzx0902 Jun 9, 2026
dfa7adf
merge: integrate latest benchmark evaluation work from dev
hopecommon Jun 10, 2026
3e5c51f
docs(report): integrate final report body
hopecommon Jun 10, 2026
251013e
docs(gap7): add 16-page final defense slides
hopecommon Jun 10, 2026
3a60334
chore: ignore .claude/ local config and skills
hopecommon Jun 10, 2026
1e9419e
Merge pull request #73 from dzx0902/jflin
hopecommon Jun 10, 2026
c2b8cd9
Add source extraction candidate workflow
xingrui-Wang Jun 14, 2026
ee623c7
Add optional LLM memory extraction pipeline
xingrui-Wang Jun 14, 2026
89ca761
Update final report, slides, and audit artifacts
xingrui-Wang Jun 14, 2026
899530e
Refresh final report artifacts and sanitize PDF exports
xingrui-Wang Jun 14, 2026
fa6ecd3
Fix chunking lint case and refresh final PDFs
xingrui-Wang Jun 14, 2026
7df991c
docs(llm): annotate llm_analysis service with docstrings and comments
hopecommon Jun 15, 2026
b038e1b
docs: organize final submission package
hopecommon Jun 15, 2026
52ba174
docs: address codex review — clean gap residue, fix moved paths, refr…
hopecommon Jun 15, 2026
3e794a6
docs: polish final submission references
hopecommon Jun 15, 2026
a2df692
docs(readme): reflect full delivered scope and optional AI features
hopecommon Jun 15, 2026
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
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,19 @@ DATABASE_URL=postgresql://memorybase:memorybase@localhost:5432/memorybase_db
BACKEND_HOST=127.0.0.1
BACKEND_PORT=8000
FRONTEND_PORT=5173
EMBEDDING_PROVIDER=local
SILICONFLOW_API_KEY=
SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1
SILICONFLOW_EMBEDDING_MODEL=Qwen/Qwen3-Embedding-0.6B
SILICONFLOW_EMBEDDING_DIMENSIONS=1024
LLM_PROVIDER=
LLM_ANALYSIS_API_KEY=
LLM_ANALYSIS_BASE_URL=https://api.openai.com/v1
LLM_ANALYSIS_MODEL=gpt-4o-mini
LLM_ANALYSIS_PROVIDER=openai-compatible
DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_CHAT_MODEL=deepseek-chat
SILICONFLOW_CHAT_MODEL=
LLM_TEMPERATURE=0.2
LLM_MAX_TOKENS=800
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Thumbs.db
.vscode/
.idea/
.claude/

# Python
__pycache__/
Expand All @@ -27,6 +28,7 @@ dist/
*.log
logs/
data/logs/
!docs/final-assets/screenshots/logs/

# Environment
.env
Expand All @@ -45,7 +47,10 @@ data/markdown_wiki/
.coverage
coverage/
htmlcov/
evaluation/outputs/*.csv
evaluation/outputs/*.md
evaluation/outputs/**
evaluation/external/*/raw/*
!evaluation/external/*/raw/.gitkeep
evaluation/external/*/processed/*
!evaluation/external/*/processed/.gitkeep
docs/prompt.md
docs/summary.md
57 changes: 25 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,25 @@ SourceDocument
- Frontend: React + Vite
- Database: PostgreSQL
- Graph Database: Neo4j(可选,用于知识图谱同步与可视化)
- Search: PostgreSQL Full Text Search
- Search: PostgreSQL Full Text Search(GIN tsvector / trigram)+ 可选 embedding 的 hybrid recall
- AI(可选): OpenAI-compatible LLM 用于候选记忆抽取与 QA;本地 hashing embedding cache,未配置外部 provider 时透明 fallback 到 keyword
- CLI: `mb` / `memorybase`(sessions / observe / remember / search / recall)
- SQL: views, triggers, indexes
- Deployment: Docker Compose

## P0 MVP
## 功能模块

- SourceDocument / SourceChunk
- MemoryItem / MemoryEvidence
- MemoryRevision / AuditLog
- RecallLog
- WikiPage / WikiPageRevision
- TimelineEntry
- Dashboard / Source / Memory / Recall / Wiki 页面
- Source 导入与 chunk 切分:SourceDocument / SourceChunk
- 记忆与证据链:MemoryItem / MemoryEvidence / MemoryRevision
- 治理与溯源:AuditLog、ConflictRecord 冲突治理、ForgetRequest 遗忘/归档审批、TimelineEntry
- 检索:lexical(FTS / trigram)+ 可选 embedding 的 hybrid recall(无 embedding 时透明 fallback 到 keyword),并投影为 Context Pack
- 权限与可见性:AccessPolicy、agent-aware visibility
- 候选记忆抽取:rule-based + 可选 LLM(结果写入 `status='candidate'`,需人工审批后进入 active)
- Wiki 投影:WikiPage / WikiPageRevision,可导出 Markdown
- Graph Explorer:PostgreSQL preview + 可选 Neo4j 同步的 provenance 图谱
- CLI / Agent Runtime:`mb` sessions / observe / remember / search / recall
- 评测:LoCoMo / LongMemEval / MemoryAgentBench adapters 与 evaluation framework
- 前端页面:Dashboard / Source / Memory / Recall / Governance / Wiki / Runtime / Graph

## 本地运行

Expand Down Expand Up @@ -271,29 +277,16 @@ psql postgresql://memorybase:memorybase@localhost:5432/memorybase_db -c "\dt"
npm run db:check
```

## 文档目录

- docs/00-project-overview\.md
- docs/01-requirements.md
- docs/02-data-flow\.md
- docs/03-data-dictionary.md
- docs/04-er-design.md
- docs/05-logical-design.md
- docs/06-physical-design.md
- docs/07-system-architecture.md
- docs/08-api-design.md
- docs/09-module-ipo.md
- docs/10-test-plan.md
- docs/11-demo-script.md
- docs/12-github-workflow\.md
- docs/13-final-report-outline.md
- docs/14-initial-issues.md
- docs/15-api-contract-plan.md
- docs/16-agent-runtime-gap-analysis.md
- docs/17-agent-runtime-plan.md
- docs/18-pr4-lexical-search-design.md
- docs/19-repo-session-aware-context-design.md
- docs/20-course-alignment-risk-and-recovery-plan.md
## 文档与材料

项目的设计文档、演示材料、源程序说明与参考资料统一放在 [`docs/`](docs/) 目录下:

- 整合后的主报告:[`docs/final-report.md`](docs/final-report.md) / [`docs/final-report.pdf`](docs/final-report.pdf)
- 答辩 PPT:[`docs/final-assets/slides/final-defense.pdf`](docs/final-assets/slides/final-defense.pdf)
- 流程图 / ER / 时序图与系统演示截图:[`docs/final-assets/`](docs/final-assets/)
- 需求 / 概念 / 逻辑 / 物理设计、范式、索引、API、分工等专项文档:见 [`docs/`](docs/)

完整的文档导览与主题索引见 [`docs/README.md`](docs/README.md);开发过程中的规划与记录归档在 [`docs/process/`](docs/process/)。

## GitHub Workflows

Expand Down
17 changes: 17 additions & 0 deletions backend/app/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
PostgresGraphRepository,
PostgresGraphVisibilityRepository,
)
from ..services.llm_analysis import LlmAnalysisDefaults, OpenAICompatibleAnalysisClient
from ..services.llm_service import AnswerService, ChatProvider, OpenAICompatibleChatProvider
from ..services.memory_extraction_service import MemoryExtractionService
from ..services.memory_service import MemoryService, PostgresMemoryRepository
Expand All @@ -31,6 +32,8 @@
from ..services.wiki_service import PostgresWikiRepository, WikiService


# Dependency builders keep FastAPI route files thin. The graph service is cached
# because it can own a Neo4j driver pool; ordinary repositories remain cheap wrappers.
@lru_cache(maxsize=1)
def get_database() -> Database:
settings = get_settings()
Expand All @@ -53,9 +56,19 @@ def get_memory_service() -> MemoryService:


def get_memory_extraction_service() -> MemoryExtractionService:
settings = get_settings()
return MemoryExtractionService(
database=get_database(),
memory_service=get_memory_service(),
llm_client=OpenAICompatibleAnalysisClient(),
llm_defaults=LlmAnalysisDefaults(
api_key=settings.llm_analysis_api_key,
base_url=settings.llm_analysis_base_url,
model=settings.llm_analysis_model,
provider=settings.llm_analysis_provider,
temperature=settings.llm_temperature,
max_tokens=settings.llm_max_tokens,
),
)


Expand Down Expand Up @@ -138,6 +151,8 @@ def get_app_settings() -> Settings:


def _build_recall_repository(settings: Settings) -> PostgresRecallRepository:
# Recall and QA share this builder so keyword/vector/hybrid configuration stays
# consistent across direct recall, context packs, and optional answer generation.
return PostgresRecallRepository(
get_database(),
embedding_provider=_build_embedding_provider(settings),
Expand All @@ -148,6 +163,8 @@ def _build_recall_repository(settings: Settings) -> PostgresRecallRepository:


def _build_embedding_provider(settings: Settings) -> EmbeddingProvider:
# Local hashing is the deterministic course-demo default; SiliconFlow is an
# optional provider path when an external embedding API is configured.
provider = _embedding_provider_name(settings)
if provider == "siliconflow":
return SiliconFlowEmbeddingProvider(
Expand Down
25 changes: 25 additions & 0 deletions backend/app/api/memories.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from ..models.memory import (
ActorContext,
EditorType,
MemoryBatchCreateRequest,
MemoryBatchCreateResponse,
MemoryCreateRequest,
MemoryDeleteResponse,
MemoryDetailResponse,
Expand Down Expand Up @@ -41,6 +43,29 @@ def create_memory(
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc


@router.post(
"/batch",
response_model=MemoryBatchCreateResponse,
status_code=status.HTTP_201_CREATED,
)
def create_memories(
payload: MemoryBatchCreateRequest,
x_actor_type: EditorType = Header(default="user", alias="X-Actor-Type"),
x_actor_id: UUID | None = Header(default=None, alias="X-Actor-Id"),
x_revision_reason: str = Header(default="memory batch create", alias="X-Revision-Reason"),
service: MemoryService = Depends(get_memory_service),
) -> MemoryBatchCreateResponse:
try:
actor = ActorContext(
actor_type=x_actor_type,
actor_id=x_actor_id,
revision_reason=x_revision_reason,
)
return service.create_memories(payload, actor)
except MemoryValidationError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc


@router.get("", response_model=MemoryListResponse)
def list_memories(
workspace_id: UUID | None = Query(default=None),
Expand Down
3 changes: 2 additions & 1 deletion backend/app/api/memory_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ def extract_from_chunks(
actor = ActorContext(
actor_type=x_actor_type,
actor_id=x_actor_id,
revision_reason="rule-based memory extraction",
revision_reason=f"{payload.method} memory extraction",
)
candidates = service.extract_from_chunks(payload, actor)
except MemoryExtractionValidationError as exc:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
return MemoryExtractionResponse(
workspace_id=payload.workspace_id,
method=payload.method,
created_count=len(candidates),
candidates=candidates,
)
Expand Down
26 changes: 26 additions & 0 deletions backend/app/cli/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ def create_memory(
) -> dict[str, Any]:
...

def extract_candidates(
self,
payload: dict[str, Any],
*,
actor_type: str,
actor_id: str | None,
) -> dict[str, Any]:
...


class HttpMemoryBaseClient:
def __init__(self, api_base_url: str) -> None:
Expand Down Expand Up @@ -133,6 +142,23 @@ def create_memory(
headers["X-Actor-Id"] = actor_id
return self._request("POST", "/api/memories", json=payload, headers=headers)

def extract_candidates(
self,
payload: dict[str, Any],
*,
actor_type: str,
actor_id: str | None,
) -> dict[str, Any]:
headers = {"X-Actor-Type": actor_type}
if actor_id:
headers["X-Actor-Id"] = actor_id
return self._request(
"POST",
"/api/memory-extraction/from-chunks",
json=payload,
headers=headers,
)

def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
url = f"{self._api_base_url}{path}"
try:
Expand Down
101 changes: 101 additions & 0 deletions backend/app/cli/commands/extract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import annotations

import os
from pathlib import Path
from uuid import UUID

import typer

from ..client import MemoryBaseClientError, MemoryBaseServerError, build_client
from ..config import load_config
from ..output import (
EXIT_CLIENT_ERROR,
EXIT_OK,
EXIT_SERVER_ERROR,
error,
validate_format,
write_result,
)
from ..runtime import resolve_workspace_and_agent

EXTRACTION_METHODS = {"rule_based", "llm"}


def extract(
config_path: Path | None = typer.Option(None, "--config", help="Config file to read."),
api_base_url: str | None = typer.Option(None, "--api-base", help="MemoryBase API base URL."),
workspace: str | None = typer.Option(None, "--workspace", help="Workspace slug or UUID."),
agent: str | None = typer.Option(None, "--agent", help="Agent name or UUID."),
chunk_ids: list[UUID] | None = typer.Option(
None,
"--chunk",
help="Source chunk UUID. Repeat for multiple chunks.",
),
max_candidates: int = typer.Option(10, "--max-candidates", min=1, max=50),
method: str = typer.Option("rule_based", "--method", help="rule_based or llm."),
llm_api_key: str | None = typer.Option(
None,
"--llm-api-key",
help="LLM API key. Defaults to LLM_ANALYSIS_API_KEY or OPENAI_API_KEY.",
),
llm_base_url: str | None = typer.Option(
None,
"--llm-base-url",
help="OpenAI-compatible base URL.",
),
llm_model: str | None = typer.Option(None, "--llm-model", help="LLM model name."),
llm_provider: str | None = typer.Option(
None,
"--llm-provider",
help="Provider label stored in run audit metadata.",
),
output_format: str = typer.Option("json", "--format", help="json, markdown, or table."),
) -> None:
validate_format(output_format)
if method not in EXTRACTION_METHODS:
error("Unsupported extraction method. Use rule_based or llm.")
raise typer.Exit(EXIT_CLIENT_ERROR)
if not chunk_ids:
error("At least one --chunk is required.")
raise typer.Exit(EXIT_CLIENT_ERROR)

config = load_config(config_path).with_overrides(
api_base_url=api_base_url,
workspace=workspace,
agent=agent,
)
try:
client = build_client(config)
workspace_id, agent_id = resolve_workspace_and_agent(client, config)
payload = {
"workspace_id": workspace_id,
"chunk_ids": [str(chunk_id) for chunk_id in chunk_ids],
"max_candidates": max_candidates,
"method": method,
}
if method == "llm":
payload["llm"] = {
"api_key": llm_api_key
or os.getenv("LLM_ANALYSIS_API_KEY")
or os.getenv("OPENAI_API_KEY"),
"base_url": llm_base_url,
"model": llm_model,
"provider": llm_provider,
}
payload["llm"] = {
key: value for key, value in payload["llm"].items() if value is not None
}
result = client.extract_candidates(
payload,
actor_type=config.actor_type,
actor_id=config.actor_id or agent_id,
)
except MemoryBaseClientError as exc:
error(str(exc))
raise typer.Exit(EXIT_CLIENT_ERROR) from exc
except MemoryBaseServerError as exc:
error(str(exc))
raise typer.Exit(EXIT_SERVER_ERROR) from exc

write_result(result, output_format=output_format)
raise typer.Exit(EXIT_OK)
2 changes: 2 additions & 0 deletions backend/app/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .commands.configure import configure
from .commands.context import context
from .commands.eval import app as eval_app
from .commands.extract import extract
from .commands.health import health
from .commands.observe import observe
from .commands.recall import recall
Expand Down Expand Up @@ -41,6 +42,7 @@ def main(

app.command("configure", short_help="Configure CLI defaults")(configure)
app.command("context", short_help="Render agent context")(context)
app.command("extract", short_help="Extract candidate memories from chunks")(extract)
app.command("health", short_help="Check API, workspace, and agent health")(health)
app.command("observe", short_help="Write conversation messages")(observe)
app.command("recall", short_help="Recall governed memories")(recall)
Expand Down
Loading
Loading