|
206 | 206 | "リンク:", |
207 | 207 | "リンク:", |
208 | 208 | ) |
| 209 | +_RAW_RESULT_DETAIL_PREFIXES = ( |
| 210 | + "标题:", |
| 211 | + "标题:", |
| 212 | + "来源:", |
| 213 | + "来源:", |
| 214 | + "日期:", |
| 215 | + "日期:", |
| 216 | + "date:", |
| 217 | + "Date:", |
| 218 | + "链接:", |
| 219 | + "链接:", |
| 220 | + "link:", |
| 221 | + "Link:", |
| 222 | + "url:", |
| 223 | + "URL:", |
| 224 | + "リンク:", |
| 225 | + "リンク:", |
| 226 | + "为什么相关:", |
| 227 | + "为什么相关:", |
| 228 | + "相关性:", |
| 229 | + "相关性:", |
| 230 | + "relevance:", |
| 231 | + "Relevance:", |
| 232 | + "why relevant:", |
| 233 | + "Why relevant:", |
| 234 | +) |
| 235 | +_RAW_RESULT_COMPLETION_PREFIXES = ( |
| 236 | + "为什么相关:", |
| 237 | + "为什么相关:", |
| 238 | + "相关性:", |
| 239 | + "相关性:", |
| 240 | + "relevance:", |
| 241 | + "Relevance:", |
| 242 | + "why relevant:", |
| 243 | + "Why relevant:", |
| 244 | +) |
| 245 | +_RAW_RESULT_SOURCE_PREFIXES = ( |
| 246 | + "来源:", |
| 247 | + "来源:", |
| 248 | +) |
209 | 249 | _KNOWN_SOURCE_HOST_ALIASES = { |
210 | 250 | "cls.cn": ("财联社", "cls"), |
211 | 251 | "stcn.com": ("证券时报", "证券时报网", "stcn"), |
@@ -481,6 +521,105 @@ def _contains_non_specific_raw_date(answer: str) -> bool: |
481 | 521 | return False |
482 | 522 |
|
483 | 523 |
|
| 524 | +def _raw_line_starts_result_detail(line: str) -> bool: |
| 525 | + return line.startswith(_RAW_RESULT_DETAIL_PREFIXES) |
| 526 | + |
| 527 | + |
| 528 | +def _raw_line_starts_new_result(line: str, next_line: str) -> bool: |
| 529 | + if line.startswith(("标题:", "标题:")): |
| 530 | + return True |
| 531 | + if re.match(r"^\s*(?:[-*]|\d+[.、])\s*\S", line): |
| 532 | + return True |
| 533 | + if _raw_line_starts_result_detail(line): |
| 534 | + return False |
| 535 | + return next_line.startswith(_RAW_RESULT_SOURCE_PREFIXES) |
| 536 | + |
| 537 | + |
| 538 | +def _raw_line_completes_result(line: str) -> bool: |
| 539 | + return line.startswith(_RAW_RESULT_COMPLETION_PREFIXES) |
| 540 | + |
| 541 | + |
| 542 | +def _split_raw_result_blocks(answer: str) -> list[str]: |
| 543 | + answer = answer.strip() |
| 544 | + if not answer: |
| 545 | + return [] |
| 546 | + |
| 547 | + blocks = [ |
| 548 | + block.strip() |
| 549 | + for block in re.split(r"\n\s*\n+", answer) |
| 550 | + if block.strip() |
| 551 | + ] |
| 552 | + if len(blocks) > 1: |
| 553 | + return blocks |
| 554 | + |
| 555 | + lines = [line.strip() for line in answer.splitlines() if line.strip()] |
| 556 | + if len(lines) <= 1: |
| 557 | + return [answer] |
| 558 | + |
| 559 | + blocks = [] |
| 560 | + current: list[str] = [] |
| 561 | + current_is_complete = False |
| 562 | + for index, line in enumerate(lines): |
| 563 | + next_line = lines[index + 1] if index + 1 < len(lines) else "" |
| 564 | + if current_is_complete and _raw_line_starts_new_result(line, next_line): |
| 565 | + blocks.append("\n".join(current).strip()) |
| 566 | + current = [line] |
| 567 | + current_is_complete = False |
| 568 | + else: |
| 569 | + current.append(line) |
| 570 | + |
| 571 | + if _raw_line_completes_result(line): |
| 572 | + current_is_complete = True |
| 573 | + |
| 574 | + if current: |
| 575 | + blocks.append("\n".join(current).strip()) |
| 576 | + |
| 577 | + return blocks if len(blocks) > 1 else [answer] |
| 578 | + |
| 579 | + |
| 580 | +def _raw_result_block_has_filterable_defect(prompt_text: str, block: str) -> bool: |
| 581 | + if _contains_aggregate_raw_source_label(block): |
| 582 | + return True |
| 583 | + if _contains_non_chinese_raw_link_label(block): |
| 584 | + return True |
| 585 | + if _contains_raw_source_host_mismatch(block): |
| 586 | + return True |
| 587 | + if _contains_non_specific_raw_date(block): |
| 588 | + return True |
| 589 | + if _contains_non_specific_raw_url(block): |
| 590 | + return True |
| 591 | + return _contains_malformed_stock_code(prompt_text, block) |
| 592 | + |
| 593 | + |
| 594 | +def _normalize_raw_answer_for_prompt(prompt_text: str, answer: str) -> str: |
| 595 | + if not _prompt_requests_multiple_items(prompt_text): |
| 596 | + return answer |
| 597 | + |
| 598 | + blocks = _split_raw_result_blocks(answer) |
| 599 | + if len(blocks) <= 1: |
| 600 | + return answer |
| 601 | + |
| 602 | + filtered_blocks: list[str] = [] |
| 603 | + seen_urls: set[str] = set() |
| 604 | + changed = False |
| 605 | + for block in blocks: |
| 606 | + urls = _extract_raw_urls(block) |
| 607 | + has_duplicate_url = bool(urls) and any(url in seen_urls for url in urls) |
| 608 | + if has_duplicate_url or _raw_result_block_has_filterable_defect( |
| 609 | + prompt_text, |
| 610 | + block, |
| 611 | + ): |
| 612 | + changed = True |
| 613 | + continue |
| 614 | + filtered_blocks.append(block) |
| 615 | + seen_urls.update(urls) |
| 616 | + |
| 617 | + if not changed or not filtered_blocks: |
| 618 | + return answer |
| 619 | + |
| 620 | + return "\n\n".join(filtered_blocks) |
| 621 | + |
| 622 | + |
484 | 623 | def _is_generic_json_source_label(value: object) -> bool: |
485 | 624 | source = _normalize(str(value or "")) |
486 | 625 | return any(phrase in source for phrase in _GENERIC_JSON_SOURCE_LABEL_PHRASES) |
@@ -523,7 +662,7 @@ def _published_date_is_future(published_date: str) -> bool: |
523 | 662 | def normalize_answer_for_prompt(prompt: str, answer: str) -> str: |
524 | 663 | prompt_text = _normalize(prompt) |
525 | 664 | if not _prompt_requests_json_results(prompt_text): |
526 | | - return answer |
| 665 | + return _normalize_raw_answer_for_prompt(prompt_text, answer) |
527 | 666 |
|
528 | 667 | date_range = _extract_requested_date_range(prompt) |
529 | 668 | try: |
|
0 commit comments