文档修改
This commit is contained in:
1 parent
b8adefbb88
commit
294d06f22b
9 files changed
+83
-3
No files matched your search
@@ -32,6 +32,20 @@ ai_writing:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 1.5 检索关键词数量
|
||||||
|
|
||||||
|
素材专家的检索词数量由**用户在写作表单中指定**(「检索关键词数量」输入框,
|
||||||
|
范围 `1~10`,默认 `6`)。数量越多素材越全面,但检索与排序更耗时。
|
||||||
|
|
||||||
|
- 该参数随 `POST /api/ai-writing/start` 的 `keyword_count` 字段传入,写入 `AIWritingState.keyword_count`。
|
||||||
|
- `researcher_node` 用它决定让 LLM 生成多少个检索词、并发检索多少个方向
|
||||||
|
(`EXTRACT_KEYWORDS_USER` prompt 按该数量格式化,`search_keywords = keywords[:keyword_count]`)。
|
||||||
|
- 非法值(缺省 / 越界 / 非数字)由前后端双重兜底:后端 `_resolve_keyword_count()`
|
||||||
|
规整到 `[1, 10]`,越界回退默认 `6`。
|
||||||
|
- 补充搜索(`re_search`)时用户额外添加的关键词不会被该上限截断。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. 内网接口规格
|
## 2. 内网接口规格
|
||||||
|
|
||||||
- **地址**:`http://10.106.57.180:7863/knowledge_base/search_docs_es`
|
- **地址**:`http://10.106.57.180:7863/knowledge_base/search_docs_es`
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export async function startWritingSession(req: AIWritingRequest): Promise<string
|
|||||||
target_audience: req.targetAudience,
|
target_audience: req.targetAudience,
|
||||||
word_count_target: req.wordCountTarget,
|
word_count_target: req.wordCountTarget,
|
||||||
model_name: req.modelName ?? '',
|
model_name: req.modelName ?? '',
|
||||||
|
keyword_count: req.keywordCount,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!resp.ok) throw new Error(`启动失败: ${resp.status}`);
|
if (!resp.ok) throw new Error(`启动失败: ${resp.status}`);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) {
|
|||||||
const [articleType, setArticleType] = useState('analysis');
|
const [articleType, setArticleType] = useState('analysis');
|
||||||
const [audience, setAudience] = useState('普通读者');
|
const [audience, setAudience] = useState('普通读者');
|
||||||
const [wordCountInput, setWordCountInput] = useState('800');
|
const [wordCountInput, setWordCountInput] = useState('800');
|
||||||
|
const [keywordCountInput, setKeywordCountInput] = useState('6');
|
||||||
const [modelName, setModelName] = useState('');
|
const [modelName, setModelName] = useState('');
|
||||||
const [models, setModels] = useState<ModelOption[]>([]);
|
const [models, setModels] = useState<ModelOption[]>([]);
|
||||||
|
|
||||||
@@ -94,15 +95,27 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检索关键词数量限定 1~10,非法值回退默认 6
|
||||||
|
function handleKeywordCountBlur() {
|
||||||
|
const num = parseInt(keywordCountInput, 10);
|
||||||
|
if (isNaN(num)) {
|
||||||
|
setKeywordCountInput('6');
|
||||||
|
} else {
|
||||||
|
setKeywordCountInput(String(Math.max(1, Math.min(10, num))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!intent.trim()) return;
|
if (!intent.trim()) return;
|
||||||
const wordCount = Math.max(100, parseInt(wordCountInput, 10) || 800);
|
const wordCount = Math.max(100, parseInt(wordCountInput, 10) || 800);
|
||||||
|
const keywordCount = Math.max(1, Math.min(10, parseInt(keywordCountInput, 10) || 6));
|
||||||
onSubmit({
|
onSubmit({
|
||||||
userIntent: intent.trim(),
|
userIntent: intent.trim(),
|
||||||
articleType,
|
articleType,
|
||||||
targetAudience: audience.trim() || '普通读者',
|
targetAudience: audience.trim() || '普通读者',
|
||||||
wordCountTarget: wordCount,
|
wordCountTarget: wordCount,
|
||||||
|
keywordCount,
|
||||||
modelName: modelName || undefined,
|
modelName: modelName || undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -158,6 +171,25 @@ export function AIWritingForm({ onSubmit, disabled }: AIWritingFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<label className="text-xs font-medium text-muted-foreground">检索关键词数量(1~10)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
step={1}
|
||||||
|
className={INPUT_CLASS}
|
||||||
|
placeholder="6"
|
||||||
|
value={keywordCountInput}
|
||||||
|
onChange={(e) => setKeywordCountInput(e.target.value)}
|
||||||
|
onBlur={handleKeywordCountBlur}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<p className="text-[11px] text-muted-foreground/70">
|
||||||
|
素材收集专家用几个关键词检索,越多素材越全面但耗时更长。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-medium text-muted-foreground">目标读者</label>
|
<label className="text-xs font-medium text-muted-foreground">目标读者</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -425,6 +425,9 @@ export function AIWritingPanel() {
|
|||||||
<ReqRow label="目标字数">
|
<ReqRow label="目标字数">
|
||||||
<span className="text-muted-foreground">{state.request.wordCountTarget} 字</span>
|
<span className="text-muted-foreground">{state.request.wordCountTarget} 字</span>
|
||||||
</ReqRow>
|
</ReqRow>
|
||||||
|
<ReqRow label="检索关键词数量">
|
||||||
|
<span className="text-muted-foreground">{state.request.keywordCount} 个</span>
|
||||||
|
</ReqRow>
|
||||||
{state.request.modelName && (
|
{state.request.modelName && (
|
||||||
<ReqRow label="写作模型">
|
<ReqRow label="写作模型">
|
||||||
<span className="text-muted-foreground">{state.request.modelName}</span>
|
<span className="text-muted-foreground">{state.request.modelName}</span>
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ export interface AIWritingRequest {
|
|||||||
targetAudience: string;
|
targetAudience: string;
|
||||||
wordCountTarget: number;
|
wordCountTarget: number;
|
||||||
modelName?: string;
|
modelName?: string;
|
||||||
|
/** 素材检索关键词数量(1~10,默认 6) */
|
||||||
|
keywordCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 流式审核条目 ──────────────────────────────────────────────────────────────
|
// ── 流式审核条目 ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ class StartWritingRequest(BaseModel):
|
|||||||
target_audience: str = "普通读者"
|
target_audience: str = "普通读者"
|
||||||
word_count_target: int = 800
|
word_count_target: int = 800
|
||||||
model_name: str = ""
|
model_name: str = ""
|
||||||
|
# 素材检索关键词数量:用户指定用几个关键词检索(1~10,默认 6)
|
||||||
|
keyword_count: int = 6
|
||||||
|
|
||||||
|
|
||||||
class StartWritingResponse(BaseModel):
|
class StartWritingResponse(BaseModel):
|
||||||
@@ -230,6 +232,8 @@ async def start_writing(body: StartWritingRequest, request: Request):
|
|||||||
"article_type": body.article_type,
|
"article_type": body.article_type,
|
||||||
"target_audience": body.target_audience,
|
"target_audience": body.target_audience,
|
||||||
"word_count_target": body.word_count_target,
|
"word_count_target": body.word_count_target,
|
||||||
|
# 规整到 [1, 10],非法值回退默认 6
|
||||||
|
"keyword_count": max(1, min(10, body.keyword_count)),
|
||||||
"revision_count": 0,
|
"revision_count": 0,
|
||||||
"max_revisions": 3,
|
"max_revisions": 3,
|
||||||
"status": "researching",
|
"status": "researching",
|
||||||
|
|||||||
+22
-1
@@ -20,6 +20,19 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
_MAX_MATERIALS = 12
|
_MAX_MATERIALS = 12
|
||||||
|
|
||||||
|
# 素材检索关键词数量边界:用户可指定 1~10 个,非法值回退默认 6
|
||||||
|
_DEFAULT_KEYWORD_COUNT = 6
|
||||||
|
_MAX_KEYWORD_COUNT = 10
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_keyword_count(raw: object) -> int:
|
||||||
|
"""把用户传入的检索关键词数量规整到 [1, _MAX_KEYWORD_COUNT],非法值回退默认 6。"""
|
||||||
|
try:
|
||||||
|
n = int(raw) # type: ignore[arg-type]
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return _DEFAULT_KEYWORD_COUNT
|
||||||
|
return max(1, min(_MAX_KEYWORD_COUNT, n))
|
||||||
|
|
||||||
|
|
||||||
def _notice(title: str, detail: str, raw: str = "") -> dict:
|
def _notice(title: str, detail: str, raw: str = "") -> dict:
|
||||||
"""构造一个非致命告警事件,前端在时间线显示「标题 + 真实原因」,不打断写作流程。
|
"""构造一个非致命告警事件,前端在时间线显示「标题 + 真实原因」,不打断写作流程。
|
||||||
@@ -41,6 +54,9 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict
|
|||||||
intervention = state.get("last_user_intervention") or {}
|
intervention = state.get("last_user_intervention") or {}
|
||||||
extra_keywords: list[str] = intervention.get("_extra_keywords") or intervention.get("extra_keywords") or []
|
extra_keywords: list[str] = intervention.get("_extra_keywords") or intervention.get("extra_keywords") or []
|
||||||
|
|
||||||
|
# 用户指定的检索关键词数量(1~10),决定生成多少个检索词、检索多少个方向
|
||||||
|
keyword_count = _resolve_keyword_count(state.get("keyword_count"))
|
||||||
|
|
||||||
# SSE 实时进度推送(与 outline/draft 节点共用 sse_queue 机制)
|
# SSE 实时进度推送(与 outline/draft 节点共用 sse_queue 机制)
|
||||||
cfg = config.get("configurable", {})
|
cfg = config.get("configurable", {})
|
||||||
sse_queue = cfg.get("sse_queue")
|
sse_queue = cfg.get("sse_queue")
|
||||||
@@ -68,6 +84,7 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict
|
|||||||
user_intent=intent,
|
user_intent=intent,
|
||||||
article_type=article_type,
|
article_type=article_type,
|
||||||
target_audience=target_audience,
|
target_audience=target_audience,
|
||||||
|
keyword_count=keyword_count,
|
||||||
),
|
),
|
||||||
fallback={"keywords": [intent]},
|
fallback={"keywords": [intent]},
|
||||||
config=config,
|
config=config,
|
||||||
@@ -80,7 +97,11 @@ async def researcher_node(state: AIWritingState, config: RunnableConfig) -> dict
|
|||||||
# Step 2: 并发搜索
|
# Step 2: 并发搜索
|
||||||
provider = get_search_provider(ai_writing_config)
|
provider = get_search_provider(ai_writing_config)
|
||||||
|
|
||||||
search_keywords = keywords[:6]
|
# 按用户指定的关键词数量截断;补充搜索时不截断用户额外添加的关键词
|
||||||
|
search_limit = keyword_count
|
||||||
|
if extra_keywords:
|
||||||
|
search_limit = max(search_limit, len(keywords))
|
||||||
|
search_keywords = keywords[:search_limit]
|
||||||
total = len(search_keywords)
|
total = len(search_keywords)
|
||||||
await _emit(f"检索词已生成,开始检索 {total} 个方向…")
|
await _emit(f"检索词已生成,开始检索 {total} 个方向…")
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -3,7 +3,7 @@ EXTRACT_KEYWORDS_SYSTEM = """你是一名专业情报研究员(素材收集专
|
|||||||
你的工作背景:你在一家权威媒体机构任职,熟悉各类信息源的可信度评估。
|
你的工作背景:你在一家权威媒体机构任职,熟悉各类信息源的可信度评估。
|
||||||
|
|
||||||
你只能做以下事情:
|
你只能做以下事情:
|
||||||
1. 从写作意图中提取 3-6 个搜索关键词
|
1. 从写作意图中按要求的数量提取搜索关键词
|
||||||
你不能写文章,不能评价内容好坏,只做关键词提取。
|
你不能写文章,不能评价内容好坏,只做关键词提取。
|
||||||
|
|
||||||
【严禁思考】
|
【严禁思考】
|
||||||
@@ -25,7 +25,8 @@ EXTRACT_KEYWORDS_USER = """写作意图:{user_intent}
|
|||||||
文章类型:{article_type}
|
文章类型:{article_type}
|
||||||
目标读者:{target_audience}
|
目标读者:{target_audience}
|
||||||
|
|
||||||
请提取 3-6 个搜索关键词,用于搜集相关素材。只输出 JSON:
|
请提取 {keyword_count} 个搜索关键词,用于搜集相关素材。
|
||||||
|
关键词数量必须为 {keyword_count} 个,不多不少。只输出 JSON:
|
||||||
{{"keywords": ["关键词1", "关键词2"]}}"""
|
{{"keywords": ["关键词1", "关键词2"]}}"""
|
||||||
|
|
||||||
RANK_MATERIALS_SYSTEM = """你是一名专业情报研究员(素材收集专家)。
|
RANK_MATERIALS_SYSTEM = """你是一名专业情报研究员(素材收集专家)。
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ class AIWritingState(TypedDict):
|
|||||||
article_type: str
|
article_type: str
|
||||||
target_audience: str
|
target_audience: str
|
||||||
word_count_target: int
|
word_count_target: int
|
||||||
|
# 素材检索关键词数量(用户指定,1~10,默认 6)
|
||||||
|
keyword_count: int
|
||||||
|
|
||||||
# Agent A 输出
|
# Agent A 输出
|
||||||
material_package: Optional[MaterialPackage]
|
material_package: Optional[MaterialPackage]
|
||||||
|
|||||||
Reference in new issue
Block a user