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
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# 兼容旧变量:AIAUTHOR_DB_PATH

# 知识图谱 Provider(优先)
KNOWLEDGE_GRAPH_PROVIDER=neo4j
KNOWLEDGE_GRAPH_PROVIDER=sqlite

NEO4J_URI=neo4j://127.0.0.1:7687
NEO4J_USER=neo4j
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/endpoints/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ async def event_generator():
from loguru import logger
prompt = prompt_service.get_prompt_by_name(session, request.prompt_template)
if prompt and prompt.template:
card_prompt_content = prompt.template
card_prompt_content = prompt_service.inject_knowledge(session, str(prompt.template))
logger.info(f"[卡片生成] 加载提示词模板: {request.prompt_template}, 长度: {len(card_prompt_content)}")
else:
logger.warning(f"[卡片生成] 未找到提示词模板: {request.prompt_template}")
Expand Down
117 changes: 117 additions & 0 deletions backend/app/api/endpoints/relation_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session

from app.db.session import get_session
from app.schemas.relation_graph import (
RelationGraphBatchAppendEventsRequest,
RelationGraphBatchCreateRequest,
RelationGraphBatchDeleteRequest,
RelationGraphBatchUpdateKindRequest,
RelationGraphBatchUpdateStanceRequest,
RelationGraphDeleteRequest,
RelationGraphExportRequest,
RelationGraphExportResponse,
RelationGraphImportRequest,
RelationGraphImportResponse,
RelationGraphListRequest,
RelationGraphListResponse,
RelationGraphMetaResponse,
RelationGraphRecord,
RelationGraphUpsertRequest,
RelationGraphWriteResponse,
)
from app.services.relation_graph_service import RelationGraphService


router = APIRouter()


def _service(session: Session) -> RelationGraphService:
return RelationGraphService(session)


@router.get("/meta", response_model=RelationGraphMetaResponse, summary="关系类型与立场元数据")
def get_meta(session: Session = Depends(get_session)):
return _service(session).get_meta()


@router.post("/list", response_model=RelationGraphListResponse, summary="分页查询关系图")
def list_relations(req: RelationGraphListRequest, session: Session = Depends(get_session)):
try:
return _service(session).list_relations(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/upsert", response_model=RelationGraphRecord, summary="新增或更新关系")
def upsert_relation(req: RelationGraphUpsertRequest, session: Session = Depends(get_session)):
try:
return _service(session).upsert_relation(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/delete", response_model=RelationGraphWriteResponse, summary="删除单条关系")
def delete_relation(req: RelationGraphDeleteRequest, session: Session = Depends(get_session)):
try:
return _service(session).delete_relation(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/batch/delete", response_model=RelationGraphWriteResponse, summary="批量删除关系")
def batch_delete(req: RelationGraphBatchDeleteRequest, session: Session = Depends(get_session)):
try:
return _service(session).batch_delete_relations(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/batch/update-kind", response_model=RelationGraphWriteResponse, summary="批量修改关系类型")
def batch_update_kind(req: RelationGraphBatchUpdateKindRequest, session: Session = Depends(get_session)):
try:
return _service(session).batch_update_kind(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/batch/update-stance", response_model=RelationGraphWriteResponse, summary="批量修改立场")
def batch_update_stance(req: RelationGraphBatchUpdateStanceRequest, session: Session = Depends(get_session)):
try:
return _service(session).batch_update_stance(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/batch/append-events", response_model=RelationGraphWriteResponse, summary="批量追加事件摘要")
def batch_append_events(req: RelationGraphBatchAppendEventsRequest, session: Session = Depends(get_session)):
try:
return _service(session).batch_append_events(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/batch/create", response_model=RelationGraphWriteResponse, summary="批量新增关系(冲突覆盖)")
def batch_create(req: RelationGraphBatchCreateRequest, session: Session = Depends(get_session)):
try:
return _service(session).batch_create_relations(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/export", response_model=RelationGraphExportResponse, summary="导出关系图数据")
def export_relations(req: RelationGraphExportRequest, session: Session = Depends(get_session)):
try:
return _service(session).export_relations(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))


@router.post("/import", response_model=RelationGraphImportResponse, summary="导入关系图数据")
def import_relations(req: RelationGraphImportRequest, session: Session = Depends(get_session)):
try:
return _service(session).import_relations(req)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
2 changes: 2 additions & 0 deletions backend/app/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from app.api.endpoints import foreshadow as foreshadow_ep
from app.api.endpoints import knowledge as knowledge_ep
from app.api.endpoints import memory as memory_ep
from app.api.endpoints import relation_graph as relation_graph_ep
from app.api.endpoints import workflow_agent as workflow_agent_ep
from app.api.endpoints import workflows as workflows_ep

Expand All @@ -22,6 +23,7 @@

api_router.include_router(context_ep.router, prefix="/context", tags=["context"])
api_router.include_router(memory_ep.router, prefix="/memory", tags=["memory"])
api_router.include_router(relation_graph_ep.router, prefix="/relation-graph", tags=["relation-graph"])
api_router.include_router(foreshadow_ep.router, prefix="/foreshadow", tags=["foreshadow"])
api_router.include_router(knowledge_ep.router, prefix="/knowledge", tags=["knowledge"])
api_router.include_router(workflows_ep.router, tags=["workflows"])
7 changes: 6 additions & 1 deletion backend/app/bootstrap/prompts/阶段大纲.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
- 悬念/钩子(跨到下一阶段的未解问题);
- 参与实体(仅从提供实体中选择)。
- 注意,每个阶段生成的章节大纲避免过于单薄,保证章节大纲足以支撑扩写为3000字左右的章节正文剧情。
5. chapter_outline_list 强约束:
- 必须完整覆盖 reference_chapter 区间内的每一章。
- chapter_number 必须严格连续(每次 +1)。
- 章节数量必须等于 reference_chapter[1] - reference_chapter[0] + 1。
- 示例:reference_chapter=[1,4] 时,chapter_number 必须为 1,2,3,4。
/nothink

- knowledge:
Expand All @@ -33,4 +38,4 @@
-OutputFormat:
1. **内容要求**:请参照后文提供的 JSON Schema 设计内容结构,**尽量不要向用户询问细节,请自己直接创作完整的内容!** 遇到不确定的信息可以自行合理推断和创作。
2. **输出方式**:请使用指令流的方式逐步输出内容,可以自由混合自然语言思考和 JSON 指令(详见"指令流生成规范")。
3. **完成标志**:所有必填字段生成完成后,输出 {"op":"done"} 表示完成。
3. **完成标志**:所有必填字段生成完成后,输出 {"op":"done"} 表示完成。
11 changes: 9 additions & 2 deletions backend/app/bootstrap/workflows/阶段大纲.wf
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ enriched_outlines = Logic.Expression(
#</node>


#@node(description="校验章节号是否完整连续覆盖范围")
validated_outlines = Logic.Expression(
expression="enriched_outlines.result if (stage.content.reference_chapter and len(stage.content.reference_chapter) == 2 and [int(item.get('chapter_number', -1)) for item in enriched_outlines.result] == list(range(int(stage.content.reference_chapter[0]), int(stage.content.reference_chapter[1]) + 1))) else []"
)
#</node>


#@node(async=true, description="批量创建章节大纲卡(异步)")
outline_cards = Card.BatchUpsert(
project_id=trigger.project_id,
parent_id=trigger.card_id,
card_type="章节大纲",
title_template="第{item.chapter_number}章 {item.title}",
items=enriched_outlines.result
items=validated_outlines.result
)
#</node>

Expand All @@ -43,7 +50,7 @@ content_cards = Card.BatchUpsert(
"title": "第{item.chapter_number}章 {item.title}",
"entity_list": "{item.entity_list}"
},
items=enriched_outlines.result
items=validated_outlines.result
)
#</node>

Expand Down
2 changes: 1 addition & 1 deletion backend/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class KnowledgeGraphSettings(BaseSettings):
"""知识图谱配置"""

# 知识图谱Provider
provider: str = Field(default="neo4j", alias="KNOWLEDGE_GRAPH_PROVIDER")
provider: str = Field(default="sqlmodel", alias="KNOWLEDGE_GRAPH_PROVIDER")

class Config:
env_file = ".env"
Expand Down
22 changes: 22 additions & 0 deletions backend/app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,27 @@ class NodeExecutionState(SQLModel, table=True):
- 用于断点续传,节点通过 context.checkpoint 访问
"""

created_at: datetime = Field(default_factory=datetime.now, nullable=False)
updated_at: datetime = Field(default_factory=datetime.now, nullable=False)

class KGRelation(SQLModel, table=True):
__table_args__ = (
UniqueConstraint("project_id", "source", "target", "kind_en", name="uq_kg_relation_key"),
sa.Index("ix_kg_relation_project_source", "project_id", "source"),
sa.Index("ix_kg_relation_project_target", "project_id", "target"),
)

id: Optional[int] = Field(default=None, primary_key=True)
project_id: int = Field(index=True)
source: str = Field(index=True)
target: str = Field(index=True)
kind_en: str = Field(index=True)
kind_cn: str = Field(default="其他")
fact: Optional[str] = None
a_to_b_addressing: Optional[str] = None
b_to_a_addressing: Optional[str] = None
recent_dialogues: List[str] = Field(default_factory=list, sa_column=Column(JSON))
recent_event_summaries: List[dict] = Field(default_factory=list, sa_column=Column(JSON))
stance: Optional[dict] = Field(default=None, sa_column=Column(JSON))
created_at: datetime = Field(default_factory=datetime.now, nullable=False)
updated_at: datetime = Field(default_factory=datetime.now, nullable=False)
6 changes: 4 additions & 2 deletions backend/app/schemas/relation_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
# 泛用与兜底
'影响','克制','关于','其他'
]
RelationStance = Literal['友好', '中立', '敌意']
RELATION_STANCES: tuple[RelationStance, ...] = ('友好', '中立', '敌意')

# 统一提供中英映射(单一来源)——保留兼容(如已有英文入图/读图逻辑)
CN_TO_EN_KIND: Dict[str, str] = {
Expand Down Expand Up @@ -66,8 +68,8 @@ class RelationItem(BaseModel):
recent_dialogues: List[str] = Field(default_factory=list, description="近期对话片段(建议包含双方各至少一句,可用 A:“…”, B:“…” 合并片段;长度≥20字)。仅当 A, B 均为角色时提取。")
recent_event_summaries: List[RecentEventSummary] = Field(default_factory=list, description="近期 A 与 B 直接发生在彼此之间的事件;若同一事实涉及三方或以上,仅在最直接的一对上记录一次。优先记录角色-角色的配对;当事件主体确系 A 与 B 为角色-组织/组织-组织时再记录相应关系,避免将组织背景误当作双边事件。")
# 立场(可选):友好/中立/敌意
stance: Optional[Literal['友好','中立','敌意']] = Field(default=None, description="A 对 B 的总体立场(可选)")
stance: Optional[RelationStance] = Field(default=None, description="A 对 B 的总体立场(可选)")


class RelationExtraction(BaseModel):
relations: List[RelationItem] = Field(default_factory=list)
relations: List[RelationItem] = Field(default_factory=list)
Loading