Aira Thalca commited on
Commit
58af945
·
1 Parent(s): a4c6732

add streamlit app

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .env
2
+ __pycache__/
3
+ lib/
README.md CHANGED
@@ -10,3 +10,60 @@ pinned: false
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
13
+
14
+ # Education Book Generator Crew
15
+
16
+ Welcome to the Education Book Generator Crew project, powered by [crewAI](https://crewai.com). This template is designed to help you set up a multi-agent AI system with ease, leveraging the powerful and flexible framework provided by crewAI. Our goal is to enable your agents to collaborate effectively on complex tasks, maximizing their collective intelligence and capabilities.
17
+
18
+ ## Installation
19
+
20
+ Ensure you have Python >=3.10 <3.13 installed on your system. This project uses [UV](https://docs.astral.sh/uv/) for dependency management and package handling, offering a seamless setup and execution experience.
21
+
22
+ First, if you haven't already, install uv:
23
+
24
+ ```bash
25
+ pip install uv
26
+ ```
27
+
28
+ Next, navigate to your project directory and install the dependencies:
29
+
30
+ (Optional) Lock the dependencies and install them by using the CLI command:
31
+ ```bash
32
+ crewai install
33
+ ```
34
+
35
+ ### Customizing
36
+
37
+ **Add your `OPENAI_API_KEY` into the `.env` file**
38
+
39
+ - Modify `src/educational_books/config/agents.yaml` to define your agents
40
+ - Modify `src/educational_books/config/tasks.yaml` to define your tasks
41
+ - Modify `src/educational_books/crew.py` to add your own logic, tools and specific args
42
+ - Modify `src/educational_books/main.py` to add custom inputs for your agents and tasks
43
+
44
+ ## Running the Project
45
+
46
+ To kickstart your crew of AI agents and begin task execution, run this from the root folder of your project:
47
+
48
+ ```bash
49
+ crewai run
50
+ ```
51
+
52
+ This command initializes the educational_books Crew, assembling the agents and assigning them tasks as defined in your configuration.
53
+
54
+ This example, unmodified, will run the create a `report.md` file with the output of a research on LLMs in the root folder.
55
+
56
+ ## Understanding Your Crew
57
+
58
+ The educational_books Crew is composed of multiple AI agents, each with unique roles, goals, and tools. These agents collaborate on a series of tasks, defined in `config/tasks.yaml`, leveraging their collective skills to achieve complex objectives. The `config/agents.yaml` file outlines the capabilities and configurations of each agent in your crew.
59
+
60
+ ## Support
61
+
62
+ For support, questions, or feedback regarding the {{crew_name}} Crew or crewAI.
63
+
64
+ - Visit our [documentation](https://docs.crewai.com)
65
+ - Reach out to us through our [GitHub repository](https://github.com/joaomdmoura/crewai)
66
+ - [Join our Discord](https://discord.com/invite/X4JWnZnxPb)
67
+ - [Chat with our docs](https://chatg.pt/DWjSBZn)
68
+
69
+ Let's create wonders together with the power and simplicity of crewAI.
app.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from src.educational_books.main import BookFlow
3
+ import threading
4
+ import time
5
+
6
+ def reset_state():
7
+ st.session_state.book_outline = None
8
+ st.session_state.book = None
9
+ st.session_state.title = None
10
+ st.session_state.book_flow = None
11
+ st.session_state.section_finished = 0
12
+ st.session_state.total_sections = 0
13
+
14
+ def start_generation(topic):
15
+ book_flow = BookFlow()
16
+ st.session_state.book_flow = book_flow
17
+ def run_kickoff():
18
+ book_flow.kickoff(inputs={"topic": topic})
19
+ kickoff_thread = threading.Thread(target=run_kickoff)
20
+ kickoff_thread.start()
21
+
22
+ def update_state():
23
+ book_flow = st.session_state.get("book_flow", None)
24
+ if book_flow is not None:
25
+ if hasattr(book_flow.state, "book_outline") and book_flow.state.book_outline and st.session_state.book_outline is None:
26
+ st.session_state.book_outline = book_flow.state.book_outline_md
27
+ st.session_state.title = book_flow.state.title
28
+ st.session_state.total_sections = len(book_flow.state.book_outline)
29
+ if hasattr(book_flow.state, "section_finished") and book_flow.state.section_finished != st.session_state.section_finished:
30
+ st.session_state.section_finished = book_flow.state.section_finished
31
+ if hasattr(book_flow.state, "book") and book_flow.state.book and st.session_state.book is None:
32
+ st.session_state.book = book_flow.state.book_md
33
+ st.session_state.generation_started = False
34
+
35
+ st.title("Educational Book Generator")
36
+ topic = st.text_input("Enter the topic for the book:", "Bubble Sort")
37
+
38
+ if "generation_started" not in st.session_state:
39
+ st.session_state.generation_started = False
40
+ if "book_outline" not in st.session_state:
41
+ st.session_state.book_outline = None
42
+ if "book" not in st.session_state:
43
+ st.session_state.book = None
44
+ if "title" not in st.session_state:
45
+ st.session_state.title = None
46
+ if "book_flow" not in st.session_state:
47
+ st.session_state.book_flow = None
48
+ if "section_finished" not in st.session_state:
49
+ st.session_state.section_finished = 0
50
+
51
+ # Button to start generation
52
+ if st.button("Generate Book"):
53
+ st.session_state.generation_started = True
54
+ reset_state()
55
+ start_generation(topic)
56
+
57
+ # Show "Generating book..." message if the process has started
58
+ if st.session_state.generation_started:
59
+ if st.session_state.book_outline is None:
60
+ st.write("Generating book outline... Please wait.")
61
+ elif st.session_state.book is None:
62
+ st.write("Book Outline is ready")
63
+ outline_filename = f"{st.session_state.title.replace(' ', '_')}_outline.md"
64
+ st.download_button(
65
+ label="Download Book Outline",
66
+ data=st.session_state.book_outline,
67
+ file_name=outline_filename,
68
+ mime="text/markdown"
69
+ )
70
+ st.progress(st.session_state.section_finished / st.session_state.total_sections, text=f"Generating book content... {st.session_state.section_finished}/{st.session_state.total_sections}")
71
+ elif st.session_state.book_outline is not None and st.session_state.book is not None:
72
+ st.write("Book Outline is ready")
73
+ outline_filename = f"{st.session_state.title.replace(' ', '_')}_outline.md"
74
+ st.download_button(
75
+ label="Download Book Outline",
76
+ data=st.session_state.book_outline,
77
+ file_name=outline_filename,
78
+ mime="text/markdown"
79
+ )
80
+ st.write("Book content is ready")
81
+ content_filename = f"{st.session_state.title.replace(' ', '_')}.md"
82
+ st.download_button(
83
+ label="Download Generated Book",
84
+ data=st.session_state.book,
85
+ file_name=content_filename,
86
+ mime="text/markdown"
87
+ )
88
+ if st.session_state.generation_started:
89
+ time.sleep(5)
90
+ update_state()
91
+ st.rerun()
pyproject.toml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "educational_books"
3
+ version = "0.1.0"
4
+ description = "educational_books using crewAI"
5
+ authors = [{ name = "Your Name", email = "you@example.com" }]
6
+ requires-python = ">=3.10,<3.13"
7
+ dependencies = [
8
+ "crewai[tools]>=0.95.0,<1.0.0"
9
+ ]
10
+
11
+ [project.scripts]
12
+ kickoff = "educational_books.main:kickoff"
13
+ plot = "educational_books.main:plot"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.crewai]
20
+ type = "flow"
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ crewai
2
+ crewai-tools
3
+ pydantic
4
+ langchain-groq
5
+ langchain-community
6
+ streamlit
src/educational_books/__init__.py ADDED
File without changes
src/educational_books/crews/book_content/book_content.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from crewai import Agent, Crew, Process, Task, LLM
2
+ from crewai.project import CrewBase, agent, crew, task
3
+ from crewai_tools import SerperDevTool
4
+ from dotenv import load_dotenv
5
+ import streamlit as st
6
+ import os
7
+
8
+ from educational_books.types import Section
9
+
10
+ load_dotenv()
11
+ if not os.getenv("GEMINI_API_KEY"):
12
+ os.environ["GEMINI_API_KEY"] = st.secrets["google_api_key"]
13
+
14
+ if not os.getenv("SERPER_API_KEY"):
15
+ os.environ["SERPER_API_KEY"] = st.secrets["serper_api_key"]
16
+
17
+ @CrewBase
18
+ class BookContentCrew():
19
+ """BookContent crew"""
20
+ agents_config = 'config/agents.yaml'
21
+ tasks_config = 'config/tasks.yaml'
22
+ llm = LLM(model="gemini/gemini-2.0-flash-exp")
23
+
24
+ @agent
25
+ def researcher(self) -> Agent:
26
+ return Agent(config=self.agents_config['researcher'], llm=self.llm, tools=[SerperDevTool()])
27
+
28
+ @agent
29
+ def writer(self) -> Agent:
30
+ return Agent(config=self.agents_config['writer'], llm=self.llm, verbose=True)
31
+
32
+ @task
33
+ def research_section(self) -> Task:
34
+ return Task(config=self.tasks_config['research_section'], agent=self.researcher())
35
+
36
+ @task
37
+ def write_section(self) -> Task:
38
+ return Task(config=self.tasks_config['write_section'], agent=self.writer(), output_pydantic=Section)
39
+
40
+ @crew
41
+ def crew(self) -> Crew:
42
+ """Creates the Bookoutline crew"""
43
+ return Crew(
44
+ agents=self.agents,
45
+ tasks=self.tasks,
46
+ process=Process.sequential,
47
+ max_rpm=10,
48
+ )
src/educational_books/crews/book_content/config/agents.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ researcher:
2
+ role: >
3
+ Research Agent
4
+ goal: >
5
+ Gather comprehensive information about {topic} and {section_title} that will be used to create a well-informed and engaging section for the book.
6
+ Here is the outline description for the section:\n\n {section_description}
7
+ Here is the skill or key points that will be covered in this section:\n\n {covered_skills}
8
+ backstory: >
9
+ You are an experienced researcher skilled in finding the most relevant and up-to-date information on any given topic.
10
+ Your job is to provide educational content that is backed by solid research and reliable sources.
11
+
12
+ writer:
13
+ role: >
14
+ Section Writer
15
+ goal: >
16
+ Write a well-structured section for the book based on the provided section title and outline.
17
+ The section should be written in markdown format and contain around 2,000 words.
18
+ If there are code snippets or examples, they need to be included in the section
19
+ backstory: >
20
+ You are an exceptional writer, known for producing engaging, well-researched, and informative content.
21
+ You excel at transforming complex ideas into readable and well-organized sections.
src/educational_books/crews/book_content/config/tasks.yaml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ research_section:
2
+ description: >
3
+ Research the provided section topic, title, and outline to gather additional content that will be helpful in writing the section.
4
+ Ensure you focus on reliable, high-quality sources of information.
5
+
6
+ Here is the outline description for the section:\n\n {section_description}
7
+ Here is the skill or key points that will be covered in this section:\n\n {covered_skills}
8
+
9
+ When researching, consider the following key points:
10
+ - you need to gather enough information to write a 2,000-word section
11
+ - The section you are researching needs to fit in well with the rest of the sections in the book.
12
+
13
+ Here is the outline of the entire book:\n\n
14
+ {book_outline}
15
+ expected_output: >
16
+ A set of reliable and high-quality content that will be used to write the section. This content should be well-organized and relevant to the section topic.
17
+
18
+ write_section:
19
+ description: >
20
+ Write a well-structured section based on the section title, goal, and outline description.
21
+ Each section should be written in markdown and should contain around 2,000 words.
22
+
23
+ Here is the topic for the book: {topic}
24
+ Here is the title of the section: {section_title}
25
+ Here is the outline description for the section:\n\n {section_description}
26
+ Here is the skill or key points that will be covered in this section:\n\n {covered_skills}
27
+
28
+ Important notes:
29
+ - The section you are writing needs to fit in well with the rest of the sections in the book.
30
+
31
+ Here is the outline of the entire book:\n\n
32
+ {book_outline}
33
+ expected_output: >
34
+ A markdown-formatted section of around 2,000 words that covers the provided section title and outline description.
src/educational_books/crews/book_outline/book_outline.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from crewai import Agent, Crew, Process, Task, LLM
2
+ from crewai.project import CrewBase, agent, crew, task
3
+ from crewai_tools import SerperDevTool
4
+ from dotenv import load_dotenv
5
+ import streamlit as st
6
+ import os
7
+
8
+ from educational_books.types import BookOutline
9
+
10
+ load_dotenv()
11
+ if not os.getenv("GEMINI_API_KEY"):
12
+ os.environ["GEMINI_API_KEY"] = st.secrets["google_api_key"]
13
+
14
+ if not os.getenv("SERPER_API_KEY"):
15
+ os.environ["SERPER_API_KEY"] = st.secrets["serper_api_key"]
16
+
17
+ @CrewBase
18
+ class BookOutlineCrew():
19
+ """Bookoutline crew"""
20
+ agents_config = 'config/agents.yaml'
21
+ tasks_config = 'config/tasks.yaml'
22
+ llm = LLM(model="gemini/gemini-2.0-flash-exp")
23
+
24
+ @agent
25
+ def researcher(self) -> Agent:
26
+ return Agent(config=self.agents_config['researcher'], llm=self.llm, tools=[SerperDevTool()])
27
+
28
+ @agent
29
+ def planner(self) -> Agent:
30
+ return Agent(config=self.agents_config['planner'], llm=self.llm, verbose=True)
31
+
32
+ @task
33
+ def research_topic(self) -> Task:
34
+ return Task(config=self.tasks_config['research_topic'], agent=self.researcher())
35
+
36
+ @task
37
+ def generate_outline(self) -> Task:
38
+ return Task(config=self.tasks_config['generate_outline'], agent=self.planner(), output_pydantic=BookOutline)
39
+
40
+ @crew
41
+ def crew(self) -> Crew:
42
+ """Creates the Bookoutline crew"""
43
+ return Crew(
44
+ agents=self.agents,
45
+ tasks=self.tasks,
46
+ process=Process.sequential,
47
+ max_rpm=10,
48
+ )
src/educational_books/crews/book_outline/config/agents.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ researcher:
2
+ role: >
3
+ Research Agent
4
+ goal: >
5
+ Gather comprehensive information about {topic} that will be used to create an organized and well-structured educational book outline.
6
+ that can be easily understood by university students.
7
+ backstory: >
8
+ You're a seasoned researcher, known for gathering the best sources and understanding the key elements of any topic.
9
+ You aim to collect all relevant information so the book outline can be developed with the most accurate and up-to-date content.
10
+
11
+ planner:
12
+ role: >
13
+ Outlining Planner
14
+ goal: >
15
+ Develop engaging and effective outline based on {topic}. This includes breaking down {topic} into smaller,
16
+ more manageable sections that are easy for students to follow and understand. The outline should be structured
17
+ in a way that allows students to progress through the book with ease from beginner to advanced levels.
18
+ backstory: >
19
+ You're a creative and detail-oriented planner with a passion for designing engaging and effective educational book outlines.
20
+ You have a deep understanding of the key concepts and skills related to {topic}, and you're able to break down
21
+ complex topics into smaller, more manageable sections that are easy for students to follow and understand
src/educational_books/crews/book_outline/config/tasks.yaml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ research_topic:
2
+ description: >
3
+ Research the provided topic of {topic} to gather the most important information that will
4
+ be useful in creating a educational book outline. Ensure you focus on high-quality, reliable sources.
5
+ The information should focus on educational content that is relevant.
6
+ expected_output: >
7
+ A set of key points and important information about {topic} that will be used to create the outline.
8
+
9
+ generate_outline:
10
+ description: >
11
+ Create an educational book outline with sections in sequential order so that students can easily follow and understand the content in order.
12
+ Ensure that each sections has a title and a brief description that highlights what will be covered in that section.
13
+ Each sections also contain a list of skills or key concepts that will be covered.
14
+ It's important to note that the order of the sections should make sense and flow logically for students to learn effectively.
15
+ Also, make sure that you do not duplicate any sections or topics in the outline.
16
+ expected_output: >
17
+ A book title, list of outline sections with a title, brief description, list of skills or key concepts, and list of objectives that will be covered in that section.
18
+ Example:
19
+ Book Title: ...
20
+ Sections: [
21
+ {
22
+ title: ...,
23
+ description: ...,
24
+ covered_skills: [...],
25
+ objectives: [...]
26
+ },
27
+ ...
28
+ ]
29
+ Minimum of sections should be 3 and maximum should be 15.
src/educational_books/main.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ from pydantic import BaseModel
3
+ from typing import List
4
+ from crewai.flow.flow import Flow, listen, start
5
+ from educational_books.crews.book_outline.book_outline import BookOutlineCrew
6
+ from educational_books.crews.book_content.book_content import BookContentCrew
7
+ from educational_books.types import Section, SectionOutline
8
+ import asyncio
9
+
10
+ class BookState(BaseModel):
11
+ title: str = "Mastering Data Structures and Algorithms"
12
+ book: List[Section] = []
13
+ book_outline: List[SectionOutline] = []
14
+ topic: str = "Sorting Algorithms"
15
+ book_outline_md: str = ""
16
+ book_md: str = ""
17
+ section_finished: int = 0
18
+
19
+ class BookFlow(Flow[BookState]):
20
+ initial_state = BookState
21
+
22
+ def write_to_file(self, filename: str, content: str, mode: str = "a"):
23
+ """Helper method to write content to the file"""
24
+ with open(filename, mode) as f:
25
+ f.write(content)
26
+
27
+ @start()
28
+ def initialize(self, topic: str = None):
29
+ if topic:
30
+ self.state.topic = topic
31
+
32
+ @listen(initialize)
33
+ def generate_book_outline(self):
34
+ print("Generating book outline")
35
+ crew = BookOutlineCrew()
36
+ outline = crew.crew().kickoff(inputs={"topic": self.state.topic})
37
+ self.state.title = outline["title"]
38
+ for section in outline["sections"]:
39
+ self.state.book_outline_md += f"# {section.title}\n"
40
+ self.state.book_outline_md += f"{section.description}\n\n"
41
+ self.state.book_outline_md += f"## Covered Skills\n"
42
+ for skill in section.covered_skills:
43
+ self.state.book_outline_md += f"- {skill}\n"
44
+ self.state.book_outline_md += "\n"
45
+ self.state.book_outline_md += f"## Learning Objectives\n"
46
+ for objective in section.objectives:
47
+ self.state.book_outline_md += f"- {objective}\n"
48
+ self.state.book_outline_md += "\n"
49
+ self.state.book_outline = outline["sections"]
50
+
51
+ @listen(generate_book_outline)
52
+ async def generate_book(self):
53
+ print("Generating book")
54
+ crew = BookContentCrew()
55
+ tasks = []
56
+ book_outline = [(i, section.title, section.description, section.covered_skills) for i, section in enumerate(self.state.book_outline)]
57
+
58
+ async def generate_section(section):
59
+ content = crew.crew().kickoff(inputs={
60
+ "topic": self.state.topic,
61
+ "section_title": section.title,
62
+ "section_description": section.description,
63
+ "covered_skills": section.covered_skills,
64
+ "book_outline": book_outline
65
+ })
66
+ title = content["title"]
67
+ content = content["content"]
68
+ section = Section(title=title, content=content)
69
+ section_content = f"# {title}\n\n{content}\n\n"
70
+ self.state.book_md += section_content
71
+ self.state.section_finished += 1
72
+ return section
73
+ for section in self.state.book_outline:
74
+ print(f"Generating content for {section.title}")
75
+ task = asyncio.create_task(generate_section(section))
76
+ tasks.append(task)
77
+
78
+ sections = await asyncio.gather(*tasks)
79
+ self.state.book = sections
80
+ print(f"Book generated with {len(self.state.book)} sections")
81
+
82
+ def kickoff():
83
+ book_name = BookFlow()
84
+ book_name.kickoff()
85
+
86
+ def plot():
87
+ book_name = BookFlow()
88
+ book_name.plot()
89
+
90
+
91
+ if __name__ == "__main__":
92
+ kickoff()
src/educational_books/tools/__init__.py ADDED
File without changes
src/educational_books/tools/custom_tool.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Type
2
+
3
+ from crewai.tools import BaseTool
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class MyCustomToolInput(BaseModel):
8
+ """Input schema for MyCustomTool."""
9
+
10
+ argument: str = Field(..., description="Description of the argument.")
11
+
12
+
13
+ class MyCustomTool(BaseTool):
14
+ name: str = "Name of my tool"
15
+ description: str = (
16
+ "Clear description for what this tool is useful for, your agent will need this information to use it."
17
+ )
18
+ args_schema: Type[BaseModel] = MyCustomToolInput
19
+
20
+ def _run(self, argument: str) -> str:
21
+ # Implementation goes here
22
+ return "this is an example of a tool output, ignore it and move along."
src/educational_books/types.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List
2
+ from pydantic import BaseModel
3
+ class SectionOutline(BaseModel):
4
+ title: str
5
+ description: str
6
+ covered_skills: List[str]
7
+ objectives: List[str]
8
+
9
+ class BookOutline(BaseModel):
10
+ title: str
11
+ sections: List[SectionOutline]
12
+
13
+ class Section(BaseModel):
14
+ title: str
15
+ content: str
uv.lock ADDED
The diff for this file is too large to render. See raw diff