|
|
|
|
|
|
|
|
from typing import Any, Dict, Generator |
|
|
|
|
|
from langchain.callbacks.base import BaseCallbackHandler |
|
|
from langchain.chains import LLMChain |
|
|
from langchain.prompts import PromptTemplate |
|
|
from langchain_openai import OpenAI |
|
|
|
|
|
from streaming_config import get_chunk_size, is_yield_enabled |
|
|
import os |
|
|
|
|
|
""" |
|
|
LangChain LLMChain for writing a draft based on the outline and research notes with yield support. |
|
|
""" |
|
|
DRAFT_WRITER_PROMPT = """ |
|
|
You are an expert academic writer. Given a research paper outline and research notes, write a comprehensive draft of the paper. Use clear academic language and expand on each section of the outline. |
|
|
|
|
|
Outline: |
|
|
{outline} |
|
|
|
|
|
Research notes: |
|
|
{research_notes} |
|
|
|
|
|
Instructions: |
|
|
- Write a comprehensive draft that fully develops each section of the outline |
|
|
- Use section headings and include inline citations like [1], [2] where appropriate |
|
|
- Do not include a bibliography section |
|
|
- Ensure the draft is complete and covers all points from the outline |
|
|
|
|
|
|
|
|
Draft: |
|
|
""" |
|
|
|
|
|
def get_draft_writer_chain(): |
|
|
""" |
|
|
Returns a LangChain LLMChain for draft writing. |
|
|
""" |
|
|
prompt = PromptTemplate( |
|
|
input_variables=["outline", "research_notes"], |
|
|
template=DRAFT_WRITER_PROMPT |
|
|
) |
|
|
|
|
|
|
|
|
llm = OpenAI( |
|
|
temperature=0.3, |
|
|
openai_api_key=os.getenv("OPENAI_API_KEY"), |
|
|
streaming=True |
|
|
) |
|
|
|
|
|
return LLMChain(llm=llm, prompt=prompt, output_key="draft") |
|
|
|
|
|
def yield_draft_writing(outline: str, research_notes: str, preset: str = None) -> Generator[str, None, None]: |
|
|
""" |
|
|
Yield draft writing results progressively |
|
|
|
|
|
Args: |
|
|
outline: The research paper outline |
|
|
research_notes: The research notes |
|
|
preset: Optional streaming preset |
|
|
|
|
|
Yields: |
|
|
str: Progressive draft content |
|
|
""" |
|
|
if not is_yield_enabled(preset): |
|
|
|
|
|
chain = get_draft_writer_chain() |
|
|
result = chain.run(outline=outline, research_notes=research_notes) |
|
|
yield result |
|
|
return |
|
|
|
|
|
try: |
|
|
|
|
|
from langchain.prompts import PromptTemplate |
|
|
from langchain_openai import ChatOpenAI |
|
|
|
|
|
import os |
|
|
|
|
|
|
|
|
prompt = PromptTemplate( |
|
|
input_variables=["outline", "research_notes"], |
|
|
template=DRAFT_WRITER_PROMPT |
|
|
) |
|
|
|
|
|
|
|
|
llm = ChatOpenAI( |
|
|
temperature=0.3, |
|
|
openai_api_key=os.getenv("OPENAI_API_KEY"), |
|
|
streaming=True |
|
|
) |
|
|
|
|
|
|
|
|
formatted_prompt = prompt.format(outline=outline, research_notes=research_notes) |
|
|
|
|
|
|
|
|
chunk_size = get_chunk_size("draft_writer", preset) |
|
|
|
|
|
|
|
|
result = llm.invoke(formatted_prompt) |
|
|
content = result.content |
|
|
|
|
|
|
|
|
for i in range(0, len(content), chunk_size): |
|
|
chunk = content[i:i + chunk_size] |
|
|
yield chunk |
|
|
|
|
|
except Exception as e: |
|
|
yield f"Error in draft writing: {str(e)}" |
|
|
|
|
|
def yield_draft_by_sections(outline: str, research_notes: str) -> Generator[str, None, None]: |
|
|
""" |
|
|
Yield draft writing organized by sections |
|
|
|
|
|
Args: |
|
|
outline: The research paper outline |
|
|
research_notes: The research notes |
|
|
|
|
|
Yields: |
|
|
str: Progressive draft content by section |
|
|
""" |
|
|
|
|
|
sections = [] |
|
|
lines = outline.split('\n') |
|
|
current_section = "" |
|
|
|
|
|
for line in lines: |
|
|
line = line.strip() |
|
|
if line and (line.startswith('#') or line.startswith('1.') or line.startswith('2.') or |
|
|
line.startswith('3.') or line.startswith('4.') or line.startswith('5.')): |
|
|
current_section = line |
|
|
sections.append(current_section) |
|
|
|
|
|
if not sections: |
|
|
|
|
|
yield "Writing complete draft..." |
|
|
for chunk in yield_draft_writing(outline, research_notes): |
|
|
yield chunk |
|
|
return |
|
|
|
|
|
yield f"Writing draft with {len(sections)} sections..." |
|
|
|
|
|
for i, section in enumerate(sections, 1): |
|
|
yield f"\n--- Section {i}: {section} ---" |
|
|
|
|
|
|
|
|
section_prompt = f""" |
|
|
Write the content for this specific section of the research paper: |
|
|
|
|
|
Section: {section} |
|
|
Full Outline: {outline} |
|
|
Research Notes: {research_notes} |
|
|
|
|
|
Focus on developing this section comprehensively. |
|
|
""" |
|
|
|
|
|
|
|
|
for chunk in yield_draft_writing(section_prompt, research_notes): |
|
|
yield chunk |
|
|
|
|
|
yield f"\n--- Section {i} Complete ---\n" |
|
|
|
|
|
def yield_draft_with_style(outline: str, research_notes: str, style: str = "academic") -> Generator[str, None, None]: |
|
|
""" |
|
|
Yield draft writing with specific style using yield generators |
|
|
|
|
|
Args: |
|
|
outline: The research paper outline |
|
|
research_notes: The research notes |
|
|
style: Writing style (academic, technical, accessible, etc.) |
|
|
|
|
|
Yields: |
|
|
str: Progressive draft content with specified style |
|
|
""" |
|
|
style_instructions = { |
|
|
"academic": "Use formal academic language with proper citations and scholarly tone.", |
|
|
"technical": "Focus on technical details and methodology with precise terminology.", |
|
|
"accessible": "Use clear, accessible language suitable for broader audiences.", |
|
|
"concise": "Write in a concise, direct manner with minimal elaboration." |
|
|
} |
|
|
|
|
|
style_instruction = style_instructions.get(style, style_instructions["academic"]) |
|
|
|
|
|
yield f"Writing draft in {style} style..." |
|
|
|
|
|
|
|
|
enhanced_prompt = f""" |
|
|
You are an expert academic writer. Given a research paper outline and research notes, write a comprehensive draft of the paper. |
|
|
|
|
|
Style requirement: {style_instruction} |
|
|
|
|
|
Outline: |
|
|
{outline} |
|
|
|
|
|
Research notes: |
|
|
{research_notes} |
|
|
|
|
|
Instructions: |
|
|
- Write a comprehensive draft that fully develops each section of the outline |
|
|
- Use section headings and include inline citations like [1], [2] where appropriate |
|
|
- Do not include a bibliography section |
|
|
- Ensure the draft is complete and covers all points from the outline |
|
|
- Follow the specified style: {style} |
|
|
|
|
|
Draft: |
|
|
""" |
|
|
|
|
|
|
|
|
for chunk in yield_draft_writing(enhanced_prompt, ""): |
|
|
yield chunk |
|
|
|
|
|
def process_draft_with_revisions(outline: str, research_notes: str, revisions: list = None) -> Generator[str, None, None]: |
|
|
""" |
|
|
Process draft writing with optional revision requests using yield generators |
|
|
|
|
|
Args: |
|
|
outline: The research paper outline |
|
|
research_notes: The research notes |
|
|
revisions: Optional list of revision requests |
|
|
|
|
|
Yields: |
|
|
str: Progressive draft content with revisions |
|
|
""" |
|
|
if revisions: |
|
|
yield f"Applying {len(revisions)} revision requests..." |
|
|
|
|
|
|
|
|
revision_text = "\n".join([f"- {rev}" for rev in revisions]) |
|
|
enhanced_prompt = f""" |
|
|
Outline: {outline} |
|
|
Research notes: {research_notes} |
|
|
|
|
|
Revision requests: |
|
|
{revision_text} |
|
|
|
|
|
Please incorporate these revision requests into the draft. |
|
|
""" |
|
|
else: |
|
|
enhanced_prompt = f"Outline: {outline}\nResearch notes: {research_notes}" |
|
|
|
|
|
|
|
|
for chunk in yield_draft_writing(enhanced_prompt, ""): |
|
|
yield chunk |
|
|
|