XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Handle multi-line <think> and <yapp> blocks in Yupp

Added logic to capture and process multi-line <think> and <yapp class="image-gen"> blocks referenced by special IDs. Introduced block storage and extraction functions, enabling reasoning and image-gen content to be handled via references in the response stream.

c3f8d7e7
Ammar <ammar.alkotb@gmail.com>
提交于

代码差异

1 个文件 +115 -17
Modified g4f/Provider/Yupp.py +115 -17
@@ -475,10 +475,27 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
475 475 }
476 476 # Holds leftStream / rightStream definitions to determine target/variant
477 477 select_stream = [None, None]
478
478 # State for capturing a multi-line <think> + <yapp> block (fa-style)
479 capturing_ref_id: Optional[str] = None
480 capturing_lines: List[bytes] = []
481
482 # Storage for special referenced blocks like $fa
483 think_blocks: Dict[str, str] = {}
484 image_blocks: Dict[str, str] = {}
485
479 486 def extract_ref_id(ref):
480 487 """Extract ID from reference string, e.g., from '$@123' extract '123'"""
481 488 return ref[2:] if ref and isinstance(ref, str) and ref.startswith("$@") else None
489
490 def extract_ref_name(ref: str) -> Optional[str]:
491 """Extract simple ref name from '$fa' → 'fa'"""
492 if not isinstance(ref, str):
493 return None
494 if ref.startswith("$@"):
495 return ref[2:]
496 if ref.startswith("$") and len(ref) > 1:
497 return ref[1:]
498 return None
482 499 def is_valid_content(content: str) -> bool:
483 500 """Check if content is valid"""
484 501 if not content or content in [None, "", "$undefined"]:
@@ -524,7 +541,27 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
524 541 if for_target:
525 542 normal_content += content
526 543 yield content
527
544
545 def finalize_capture_block(ref_id: str, lines: List[bytes]):
546 """Parse captured <think> + <yapp> block for a given ref ID."""
547 text = b"".join(lines).decode("utf-8", errors="ignore")
548
549 # Extract <think>...</think>
550 think_start = text.find("<think>")
551 think_end = text.find("</think>")
552 if think_start != -1 and think_end != -1 and think_end > think_start:
553 inner = text[think_start + len("<think>"):think_end].strip()
554 if inner:
555 think_blocks[ref_id] = inner
556
557 # Extract <yapp class="image-gen">...</yapp>
558 yapp_start = text.find('<yapp class="image-gen">')
559 if yapp_start != -1:
560 yapp_end = text.find("</yapp>", yapp_start)
561 if yapp_end != -1:
562 yapp_block = text[yapp_start:yapp_end + len("</yapp>")]
563 image_blocks[ref_id] = yapp_block
564
528 565 try:
529 566 line_count = 0
530 567 quick_response_id = None
@@ -540,17 +577,55 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
540 577 right_message_id = None
541 578 nudge_new_chat_id = None
542 579 nudge_new_chat = False
543
544 580 async for line in response_content:
545 581 line_count += 1
546
582 # If we are currently capturing a think/image block for some ref ID
583 if capturing_ref_id is not None:
584 capturing_lines.append(line)
585
586 # Check if this line closes the <yapp> block; after that, block is complete
587 if b"</yapp>" in line: # or b':{"curr"' in line:
588 # We may have trailing "2:{...}" after </yapp> on the same line
589 # Get id using re
590 idx = line.find(b"</yapp>")
591 suffix = line[idx + len(b"</yapp>"):]
592
593 # Finalize captured block for this ref ID
594 finalize_capture_block(capturing_ref_id, capturing_lines)
595 capturing_ref_id = None
596 capturing_lines = []
597
598 # If there is trailing content (e.g. '2:{"curr":"$fa"...}')
599 if suffix.strip():
600 # Process suffix as a new "line" in the same iteration
601 line = suffix
602 else:
603 # Nothing more on this line
604 continue
605 else:
606 # Still inside captured block; skip normal processing
607 continue
608
609 # Detect start of a <think> block assigned to a ref like 'fa:...<think>'
610 if b"<think>" in line:
611 m = line_pattern.match(line)
612 if m:
613 capturing_ref_id = m.group(1).decode()
614 capturing_lines = [line]
615 # Skip normal parsing; the rest of the block will be captured until </yapp>
616 continue
617
547 618 match = line_pattern.match(line)
548 619 if not match:
549 620 continue
550 621
551 622 chunk_id, chunk_data = match.groups()
552 623 chunk_id = chunk_id.decode()
553
624
625 if nudge_new_chat_id and chunk_id == nudge_new_chat_id:
626 nudge_new_chat = chunk_data.decode()
627 continue
628
554 629 try:
555 630 data = json.loads(chunk_data) if chunk_data != b"{}" else {}
556 631 except json.JSONDecodeError:
@@ -609,15 +684,41 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
609 684 target_stream_id = extract_ref_id(data.get("next"))
610 685 content = data.get("curr", "")
611 686 if content:
612 async for chunk in process_content_chunk(
613 content,
614 chunk_id,
615 line_count,
616 for_target=True
617 ):
618 stream["target"].append(chunk)
619 is_started = True
620 yield chunk
687 # Handle special "$fa" / "$<id>" reference
688 ref_name = extract_ref_name(content)
689 if ref_name and (ref_name in think_blocks or ref_name in image_blocks):
690 # Thinking block
691 if ref_name in think_blocks:
692 t_text = think_blocks[ref_name]
693 if t_text:
694 reasoning = Reasoning(t_text)
695 # thinking_content += t_text
696 stream["thinking"].append(reasoning)
697 # yield reasoning
698
699 # Image-gen block
700 if ref_name in image_blocks:
701 img_block_text = image_blocks[ref_name]
702 async for chunk in process_content_chunk(
703 img_block_text,
704 ref_name,
705 line_count,
706 for_target=True
707 ):
708 stream["target"].append(chunk)
709 is_started = True
710 yield chunk
711 else:
712 # Normal textual chunk
713 async for chunk in process_content_chunk(
714 content,
715 chunk_id,
716 line_count,
717 for_target=True
718 ):
719 stream["target"].append(chunk)
720 is_started = True
721 yield chunk
621 722 # Variant stream (comparison)
622 723 elif variant_stream_id and chunk_id == variant_stream_id:
623 724 yield PlainTextResponse("[Variant] " + line.decode(errors="ignore"))
@@ -660,8 +761,6 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
660 761 ...
661 762 elif chunk_id == left_message_id:
662 763 ...
663 elif chunk_id == nudge_new_chat_id:
664 nudge_new_chat = data
665 764 # Miscellaneous extra content
666 765 elif isinstance(data, dict) and "curr" in data:
667 766 content = data.get("curr", "")
@@ -684,7 +783,6 @@ class Yupp(AsyncGeneratorProvider, ProviderModelMixin):
684 783 yield PreviewResponse(variant_text)
685 784 yield JsonResponse(**stream)
686 785 log_debug(f"Finished processing {line_count} lines")
687
688 786 except:
689 787 raise
690 788