mfcli 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mfcli/.env.example +72 -0
- mfcli/__init__.py +0 -0
- mfcli/agents/__init__.py +0 -0
- mfcli/agents/controller/__init__.py +0 -0
- mfcli/agents/controller/agent.py +19 -0
- mfcli/agents/controller/config.yaml +27 -0
- mfcli/agents/controller/tools.py +42 -0
- mfcli/agents/tools/general.py +118 -0
- mfcli/alembic/env.py +61 -0
- mfcli/alembic/script.py.mako +28 -0
- mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
- mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
- mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
- mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
- mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
- mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
- mfcli/alembic.ini +147 -0
- mfcli/cli/__init__.py +0 -0
- mfcli/cli/dependencies.py +59 -0
- mfcli/cli/main.py +192 -0
- mfcli/client/__init__.py +0 -0
- mfcli/client/chroma_db.py +184 -0
- mfcli/client/docling.py +44 -0
- mfcli/client/gemini.py +252 -0
- mfcli/client/llama_parse.py +38 -0
- mfcli/client/vector_db.py +93 -0
- mfcli/constants/__init__.py +0 -0
- mfcli/constants/base_enum.py +18 -0
- mfcli/constants/directory_names.py +1 -0
- mfcli/constants/file_types.py +189 -0
- mfcli/constants/gemini.py +1 -0
- mfcli/constants/openai.py +6 -0
- mfcli/constants/pipeline_run_status.py +3 -0
- mfcli/crud/__init__.py +0 -0
- mfcli/crud/file.py +42 -0
- mfcli/crud/functional_blocks.py +26 -0
- mfcli/crud/netlist.py +18 -0
- mfcli/crud/pipeline_run.py +17 -0
- mfcli/crud/project.py +99 -0
- mfcli/digikey/__init__.py +0 -0
- mfcli/digikey/digikey.py +105 -0
- mfcli/main.py +5 -0
- mfcli/mcp/__init__.py +0 -0
- mfcli/mcp/configs/cline_mcp_settings.json +11 -0
- mfcli/mcp/configs/mfcli.mcp.json +7 -0
- mfcli/mcp/mcp_instance.py +6 -0
- mfcli/mcp/server.py +37 -0
- mfcli/mcp/state_manager.py +51 -0
- mfcli/mcp/tools/__init__.py +0 -0
- mfcli/mcp/tools/query_knowledgebase.py +108 -0
- mfcli/models/__init__.py +10 -0
- mfcli/models/base.py +10 -0
- mfcli/models/bom.py +71 -0
- mfcli/models/datasheet.py +10 -0
- mfcli/models/debug_setup.py +64 -0
- mfcli/models/file.py +43 -0
- mfcli/models/file_docket.py +94 -0
- mfcli/models/file_metadata.py +19 -0
- mfcli/models/functional_blocks.py +94 -0
- mfcli/models/llm_response.py +5 -0
- mfcli/models/mcu.py +97 -0
- mfcli/models/mcu_errata.py +26 -0
- mfcli/models/netlist.py +59 -0
- mfcli/models/pdf_parts.py +25 -0
- mfcli/models/pipeline_run.py +34 -0
- mfcli/models/project.py +27 -0
- mfcli/models/project_metadata.py +15 -0
- mfcli/pipeline/__init__.py +0 -0
- mfcli/pipeline/analysis/__init__.py +0 -0
- mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
- mfcli/pipeline/analysis/generators/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
- mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
- mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
- mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
- mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
- mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
- mfcli/pipeline/analysis/generators/generator.py +258 -0
- mfcli/pipeline/analysis/generators/generator_base.py +18 -0
- mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
- mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
- mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
- mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
- mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
- mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
- mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
- mfcli/pipeline/classifier.py +93 -0
- mfcli/pipeline/data_enricher.py +15 -0
- mfcli/pipeline/extractor.py +34 -0
- mfcli/pipeline/extractors/__init__.py +0 -0
- mfcli/pipeline/extractors/pdf.py +12 -0
- mfcli/pipeline/parser.py +120 -0
- mfcli/pipeline/parsers/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/__init__.py +0 -0
- mfcli/pipeline/parsers/netlist/edif.py +93 -0
- mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
- mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
- mfcli/pipeline/parsers/netlist/pads.py +185 -0
- mfcli/pipeline/parsers/netlist/protel.py +166 -0
- mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
- mfcli/pipeline/pipeline.py +419 -0
- mfcli/pipeline/preprocessors/__init__.py +0 -0
- mfcli/pipeline/preprocessors/user_guide.py +127 -0
- mfcli/pipeline/run_context.py +32 -0
- mfcli/pipeline/schema_mapper.py +89 -0
- mfcli/pipeline/sub_classifier.py +115 -0
- mfcli/utils/__init__.py +0 -0
- mfcli/utils/config.py +33 -0
- mfcli/utils/configurator.py +324 -0
- mfcli/utils/data_cleaner.py +82 -0
- mfcli/utils/datasheet_vectorizer.py +281 -0
- mfcli/utils/directory_manager.py +96 -0
- mfcli/utils/file_upload.py +298 -0
- mfcli/utils/files.py +16 -0
- mfcli/utils/http_requests.py +54 -0
- mfcli/utils/kb_lister.py +89 -0
- mfcli/utils/kb_remover.py +173 -0
- mfcli/utils/logger.py +28 -0
- mfcli/utils/mcp_configurator.py +311 -0
- mfcli/utils/migrations.py +18 -0
- mfcli/utils/orm.py +43 -0
- mfcli/utils/pdf_splitter.py +63 -0
- mfcli/utils/query_service.py +22 -0
- mfcli/utils/system_check.py +306 -0
- mfcli/utils/tools.py +31 -0
- mfcli/utils/vectorizer.py +28 -0
- mfcli-0.2.0.dist-info/METADATA +841 -0
- mfcli-0.2.0.dist-info/RECORD +136 -0
- mfcli-0.2.0.dist-info/WHEEL +5 -0
- mfcli-0.2.0.dist-info/entry_points.txt +3 -0
- mfcli-0.2.0.dist-info/licenses/LICENSE +21 -0
- mfcli-0.2.0.dist-info/top_level.txt +1 -0
mfcli/models/netlist.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from sqlalchemy import Column, TIMESTAMP, func
|
|
6
|
+
from sqlmodel import Field, Relationship, SQLModel
|
|
7
|
+
|
|
8
|
+
from mfcli.models.bom import BOM
|
|
9
|
+
from mfcli.models.pipeline_run import PipelineRun
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Pin(BaseModel):
|
|
13
|
+
"""Represents a single pin connection."""
|
|
14
|
+
pin: str = Field(..., description="Pin number or name")
|
|
15
|
+
net: str = Field(..., description="Net name this pin connects to")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Component(BaseModel):
|
|
19
|
+
"""Represents a component with its pins and connections."""
|
|
20
|
+
ref_des: str = Field(..., description="Reference designator (C1, R5, U2, etc.)")
|
|
21
|
+
part_number: str = Field(..., description="Part number or footprint")
|
|
22
|
+
pins: List[Pin] = Field(default_factory=list, description="List of pin connections")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NetlistSchema(BaseModel):
|
|
26
|
+
"""Top-level schema for parsed netlist."""
|
|
27
|
+
components: List[Component] = Field(default_factory=list, description="List of components")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Netlist(SQLModel, table=True):
|
|
31
|
+
__tablename__ = "netlists"
|
|
32
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
33
|
+
pipeline_run_id: int = Field(foreign_key='pipeline_runs.id', index=True, nullable=False, ondelete="CASCADE")
|
|
34
|
+
pipeline_run: Optional["PipelineRun"] = Relationship()
|
|
35
|
+
components: List["NetlistComponent"] = Relationship(back_populates='netlist', cascade_delete=True)
|
|
36
|
+
created_at: datetime = Field(
|
|
37
|
+
sa_column=Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class NetlistComponent(SQLModel, table=True):
|
|
42
|
+
__tablename__ = "netlist_components"
|
|
43
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
44
|
+
netlist_id: int = Field(foreign_key='netlists.id', index=True, nullable=False, ondelete="CASCADE")
|
|
45
|
+
netlist: Optional["Netlist"] = Relationship(back_populates='components')
|
|
46
|
+
pins: List["NetlistPin"] = Relationship(back_populates='netlist_component', cascade_delete=True)
|
|
47
|
+
ref_des: str = Field(..., max_length=100, nullable=False)
|
|
48
|
+
part_number: str = Field(max_length=255, nullable=False, index=True)
|
|
49
|
+
bom_entry_id: Optional[int] = Field(foreign_key='bom.id', index=True, ondelete='CASCADE')
|
|
50
|
+
bom_entry: Optional["BOM"] = Relationship()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NetlistPin(SQLModel, table=True):
|
|
54
|
+
__tablename__ = "netlist_pins"
|
|
55
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
56
|
+
netlist_component_id: int = Field(foreign_key='netlist_components.id', index=True, nullable=False, ondelete="CASCADE")
|
|
57
|
+
netlist_component: NetlistComponent = Relationship()
|
|
58
|
+
pin: str = Field(..., max_length=100, nullable=False)
|
|
59
|
+
net: str = Field(..., max_length=100, nullable=False)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, Integer, ForeignKey
|
|
4
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PDFPart(SQLModel, table=True):
|
|
8
|
+
__tablename__ = "pdf_parts"
|
|
9
|
+
|
|
10
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
11
|
+
file_id: int = Field(
|
|
12
|
+
sa_column=Column(
|
|
13
|
+
Integer,
|
|
14
|
+
ForeignKey("files.id", ondelete="CASCADE"),
|
|
15
|
+
nullable=False,
|
|
16
|
+
index=True
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
file: Optional["File"] = Relationship(back_populates='pdf_parts')
|
|
20
|
+
path: str = Field(max_length=600, nullable=False)
|
|
21
|
+
gemini_file_id: str = Field(max_length=40, nullable=True)
|
|
22
|
+
start_page: int = Field(nullable=True)
|
|
23
|
+
end_page: int = Field(nullable=True)
|
|
24
|
+
title: str = Field(nullable=True)
|
|
25
|
+
section_no: int = Field(nullable=True)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import Integer, func, Text, Column, DateTime, ForeignKey
|
|
5
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
6
|
+
|
|
7
|
+
from mfcli.constants.pipeline_run_status import PIPELINE_STATUS_IN_PROGRESS
|
|
8
|
+
from mfcli.models.project import Project
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PipelineRun(SQLModel, table=True):
|
|
12
|
+
__tablename__ = "pipeline_runs"
|
|
13
|
+
|
|
14
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
15
|
+
project_id: int = Field(
|
|
16
|
+
sa_column=Column(
|
|
17
|
+
Integer,
|
|
18
|
+
ForeignKey("projects.id", ondelete="CASCADE"),
|
|
19
|
+
nullable=True,
|
|
20
|
+
index=True
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
project: Optional["Project"] = Relationship(back_populates='runs')
|
|
24
|
+
status: int = Field(
|
|
25
|
+
sa_column=Column(Integer, nullable=False, server_default=str(PIPELINE_STATUS_IN_PROGRESS))
|
|
26
|
+
)
|
|
27
|
+
errors: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
|
|
28
|
+
|
|
29
|
+
created_at: datetime = Field(
|
|
30
|
+
sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
31
|
+
)
|
|
32
|
+
updated_at: datetime = Field(
|
|
33
|
+
sa_column=Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
34
|
+
)
|
mfcli/models/project.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional, List
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import Column, DateTime, func
|
|
5
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
6
|
+
|
|
7
|
+
project_name_regex = r"^[A-Za-z0-9_-]+$"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Project(SQLModel, table=True):
|
|
11
|
+
__tablename__ = "projects"
|
|
12
|
+
|
|
13
|
+
id: Optional[int] = Field(default=None, primary_key=True)
|
|
14
|
+
runs: List["PipelineRun"] = Relationship(back_populates='project', cascade_delete=True)
|
|
15
|
+
name: str = Field(
|
|
16
|
+
nullable=False,
|
|
17
|
+
unique=True,
|
|
18
|
+
index=True,
|
|
19
|
+
min_length=3,
|
|
20
|
+
max_length=45,
|
|
21
|
+
regex=project_name_regex
|
|
22
|
+
)
|
|
23
|
+
index_id: str = Field(index=True, unique=True)
|
|
24
|
+
repo_dir: str = Field(index=True, unique=True)
|
|
25
|
+
created_at: datetime = Field(
|
|
26
|
+
sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
27
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class VectorizeCheatSheetsConfig(BaseModel):
|
|
5
|
+
vectorize_errata: bool = Field(default=False)
|
|
6
|
+
vectorize_mcu: bool = Field(default=False)
|
|
7
|
+
vectorize_debug_setup: bool = Field(default=False)
|
|
8
|
+
vectorize_functional_blocks: bool = Field(default=False)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectConfig(BaseModel):
|
|
12
|
+
name: str
|
|
13
|
+
vectorize_hw_files: bool = Field(default=True)
|
|
14
|
+
vectorize_datasheets: bool = Field(default=True)
|
|
15
|
+
vectorize_cheat_sheets_config: VectorizeCheatSheetsConfig = Field(default_factory=VectorizeCheatSheetsConfig)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from mfcli.models.bom import BOM
|
|
2
|
+
from mfcli.models.file import File
|
|
3
|
+
from mfcli.models.netlist import NetlistComponent, Netlist
|
|
4
|
+
from mfcli.utils.logger import get_logger
|
|
5
|
+
from mfcli.utils.orm import Session
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def map_netlist_to_bom_entries(db: Session, pipeline_run_id: int):
|
|
11
|
+
logger.debug(f"Mapping netlist to BOM entries for pipeline: {pipeline_run_id}")
|
|
12
|
+
components: list[NetlistComponent] = (
|
|
13
|
+
db.query(NetlistComponent)
|
|
14
|
+
.join(NetlistComponent.netlist)
|
|
15
|
+
.filter(Netlist.pipeline_run_id == pipeline_run_id)
|
|
16
|
+
.all()
|
|
17
|
+
)
|
|
18
|
+
results = (
|
|
19
|
+
db.query(BOM.id, BOM.value)
|
|
20
|
+
.join(BOM.file)
|
|
21
|
+
.filter(File.pipeline_run_id == pipeline_run_id)
|
|
22
|
+
.all()
|
|
23
|
+
)
|
|
24
|
+
bom_value_id_map = {result[1]: result[0] for result in results}
|
|
25
|
+
for component in components:
|
|
26
|
+
if bom_value_id_map.get(component.part_number):
|
|
27
|
+
component.bom_entry_id = bom_value_id_map[component.part_number]
|
|
28
|
+
logger.debug(f"All netlist entries mapped: {pipeline_run_id}")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Semaphore
|
|
3
|
+
from typing import Dict, Type, List
|
|
4
|
+
|
|
5
|
+
from google.genai.types import File
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from mfcli.client.gemini import Gemini
|
|
9
|
+
from mfcli.models.bom import BOMSchema
|
|
10
|
+
from mfcli.utils.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
system_instructions = (
|
|
15
|
+
"""
|
|
16
|
+
You are the BOM Generator agent. Your role is to extract components from PDF schematics
|
|
17
|
+
and generate a Bill of Materials (BOM) in CSV format.
|
|
18
|
+
"""
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
identify_bom_entries_instructions = (
|
|
22
|
+
"""
|
|
23
|
+
You will return a list of names for Bill of Materials (BOM) entries from this schematic file.
|
|
24
|
+
"""
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
generate_bom_entry_instructions = (
|
|
28
|
+
"""
|
|
29
|
+
Analyze the schematic and extract BOM entry data for component: {}:
|
|
30
|
+
- Reference designators (R1, C1, U1, etc.)
|
|
31
|
+
- Component values (10kΩ, 100nF, etc.)
|
|
32
|
+
- Part numbers and manufacturers (if visible)
|
|
33
|
+
- Footprints/packages (if visible)
|
|
34
|
+
- Any other available information
|
|
35
|
+
"""
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BOMEntryNames(BaseModel):
|
|
40
|
+
names: List[str] = Field(default_factory=list, description="BOM entry names")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BOMGenerator:
|
|
44
|
+
def __init__(self, gemini: Gemini):
|
|
45
|
+
self._gemini = gemini
|
|
46
|
+
self._sem = Semaphore(5)
|
|
47
|
+
|
|
48
|
+
async def _generate(self, file: File, model: Type[BaseModel], instructions: str) -> BaseModel:
|
|
49
|
+
async with self._sem:
|
|
50
|
+
return await self._gemini.generate(
|
|
51
|
+
prompt=instructions,
|
|
52
|
+
instructions=system_instructions,
|
|
53
|
+
response_model=model,
|
|
54
|
+
files=[file]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async def _get_bom_entry_names(self, file: File) -> List[str]:
|
|
58
|
+
response = await self._generate(file, BOMEntryNames, identify_bom_entries_instructions)
|
|
59
|
+
return response.names
|
|
60
|
+
|
|
61
|
+
async def generate(self, file: File) -> List[Dict]:
|
|
62
|
+
bom_entry_names = await self._get_bom_entry_names(file)
|
|
63
|
+
tasks = []
|
|
64
|
+
for name in bom_entry_names:
|
|
65
|
+
bom_entry_instructions = generate_bom_entry_instructions.format(name)
|
|
66
|
+
tasks.append(self._generate(file, BOMSchema, bom_entry_instructions))
|
|
67
|
+
results: List[BOMSchema | Exception] = await asyncio.gather(*tasks, return_exceptions=True)
|
|
68
|
+
response_data = []
|
|
69
|
+
for result in results:
|
|
70
|
+
if isinstance(result, Exception):
|
|
71
|
+
logger.warn(f"There was an error generating BOM entry data: {result}")
|
|
72
|
+
continue
|
|
73
|
+
response_data.append(result.model_dump())
|
|
74
|
+
return response_data
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Semaphore
|
|
3
|
+
from typing import Dict, Type, List
|
|
4
|
+
|
|
5
|
+
from google.genai.types import File as GeminiFile
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from mfcli.models.debug_setup import (
|
|
9
|
+
DSTargetMCU,
|
|
10
|
+
DebugInterface,
|
|
11
|
+
DebugTool,
|
|
12
|
+
DriverInfo,
|
|
13
|
+
VSCodeLaunchConfig,
|
|
14
|
+
DSAdditionalTools,
|
|
15
|
+
DSTroubleshootingGuide,
|
|
16
|
+
DSQuickStartGuide
|
|
17
|
+
)
|
|
18
|
+
from mfcli.models.file import File
|
|
19
|
+
from mfcli.pipeline.analysis.generators.debug_setup.instructions import (
|
|
20
|
+
ds_mcu_instructions,
|
|
21
|
+
ds_debug_int_instructions,
|
|
22
|
+
ds_recommended_tool_instructions,
|
|
23
|
+
ds_drivers_instructions,
|
|
24
|
+
ds_vscode_launch_instructions,
|
|
25
|
+
ds_add_tools_instructions,
|
|
26
|
+
ds_troubleshooting_instructions,
|
|
27
|
+
ds_quick_start_guide_instructions,
|
|
28
|
+
ds_system_instructions
|
|
29
|
+
)
|
|
30
|
+
from mfcli.pipeline.analysis.generators.generator_base import GeneratorBase
|
|
31
|
+
from mfcli.pipeline.run_context import PipelineRunContext
|
|
32
|
+
from mfcli.utils.logger import get_logger
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
DSModelInstructionsMap = {
|
|
37
|
+
DSTargetMCU: ds_mcu_instructions,
|
|
38
|
+
DebugInterface: ds_debug_int_instructions,
|
|
39
|
+
DebugTool: ds_recommended_tool_instructions,
|
|
40
|
+
DriverInfo: ds_drivers_instructions,
|
|
41
|
+
VSCodeLaunchConfig: ds_vscode_launch_instructions,
|
|
42
|
+
DSAdditionalTools: ds_add_tools_instructions,
|
|
43
|
+
DSTroubleshootingGuide: ds_troubleshooting_instructions,
|
|
44
|
+
DSQuickStartGuide: ds_quick_start_guide_instructions
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DSCheatSheetGenerator(GeneratorBase):
|
|
49
|
+
def __init__(self, context: PipelineRunContext, db_file: File, uploads: List[GeminiFile]):
|
|
50
|
+
super().__init__(context, db_file, uploads)
|
|
51
|
+
self._sem = Semaphore(5)
|
|
52
|
+
|
|
53
|
+
async def _extract_ds_data(self, model: Type[BaseModel], instructions: str) -> BaseModel:
|
|
54
|
+
return await self._context.gemini.generate(
|
|
55
|
+
prompt=instructions,
|
|
56
|
+
instructions=ds_system_instructions,
|
|
57
|
+
response_model=model,
|
|
58
|
+
files=self._uploads
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def generate(self) -> Dict:
|
|
62
|
+
tasks = []
|
|
63
|
+
for model, instructions in DSModelInstructionsMap.items():
|
|
64
|
+
tasks.append(self._extract_ds_data(model, instructions))
|
|
65
|
+
results: list[BaseModel] = await asyncio.gather(*tasks)
|
|
66
|
+
response_dict = {}
|
|
67
|
+
for result in results:
|
|
68
|
+
response_dict.update(result.model_dump())
|
|
69
|
+
return {
|
|
70
|
+
"debug_setup": response_dict
|
|
71
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
ds_system_instructions = (
|
|
2
|
+
"""
|
|
3
|
+
You are the Debug Setup Cheat Sheet Generator agent. Your role is to analyze schematics
|
|
4
|
+
to extract debug interface information and provide comprehensive setup guidance including:
|
|
5
|
+
- Recommended JTAG/SWD tools based on the MCU and interface
|
|
6
|
+
- Driver installation for Windows and Linux
|
|
7
|
+
- VS Code launch.json configuration
|
|
8
|
+
- GDB server setup
|
|
9
|
+
- Quick start guide
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
Analyze the schematic to identify debug interface:
|
|
13
|
+
Look for:
|
|
14
|
+
- Debug connector (J-TAG, SWD, ICSP headers)
|
|
15
|
+
- Pin labels (SWDIO, SWCLK, TMS, TCK, TDI, TDO, etc.)
|
|
16
|
+
- Target MCU identification
|
|
17
|
+
- Voltage levels
|
|
18
|
+
- Connector type and pinout
|
|
19
|
+
|
|
20
|
+
Determine recommended debug tool based on MCU:
|
|
21
|
+
|
|
22
|
+
Common mappings:
|
|
23
|
+
- STM32 → ST-Link V3 (official), J-Link (professional)
|
|
24
|
+
- NXP LPC/Kinetis → LPC-Link2, J-Link
|
|
25
|
+
- Nordic nRF → J-Link, nRF DK
|
|
26
|
+
- TI MSP430/MSPM0 → MSP-FET, XDS110
|
|
27
|
+
- Microchip PIC/AVR → PICkit, ICD, MPLAB Snap
|
|
28
|
+
- ARM Cortex-M (generic) → CMSIS-DAP, J-Link
|
|
29
|
+
- ESP32 → ESP-Prog, J-Link
|
|
30
|
+
- RISC-V → J-Link, OpenOCD compatible probes
|
|
31
|
+
"""
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
ds_mcu_instructions = (
|
|
35
|
+
"""
|
|
36
|
+
Extract information systematically:
|
|
37
|
+
|
|
38
|
+
TARGET MCU:
|
|
39
|
+
- Identify the MCU from schematic
|
|
40
|
+
- Note the architecture (ARM Cortex-M4, AVR, etc.)
|
|
41
|
+
"""
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
ds_debug_int_instructions = (
|
|
45
|
+
"""
|
|
46
|
+
Extract information systematically:
|
|
47
|
+
|
|
48
|
+
DEBUG INTERFACE:
|
|
49
|
+
- interface_type: JTAG, SWD, ICSP, PDI, UPDI, etc.
|
|
50
|
+
- pin_count: Number of pins on debug connector
|
|
51
|
+
- pin_mapping: Map pin numbers to signals
|
|
52
|
+
Example: {"1": "VCC", "2": "SWDIO", "3": "GND", "4": "SWCLK"}
|
|
53
|
+
- connector_type: Physical connector (10-pin Cortex, 20-pin JTAG, etc.)
|
|
54
|
+
- voltage_level: Target voltage (typically 3.3V or 5V)
|
|
55
|
+
"""
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
ds_recommended_tool_instructions = (
|
|
59
|
+
"""
|
|
60
|
+
Extract information systematically:
|
|
61
|
+
|
|
62
|
+
RECOMMENDED TOOL:
|
|
63
|
+
- name: Primary tool recommendation (e.g., "J-Link", "ST-Link V3")
|
|
64
|
+
- model: Specific model if applicable
|
|
65
|
+
- purchase_links: Where to buy (Digikey, Mouser, official sites)
|
|
66
|
+
- compatibility_notes: Why this tool is recommended
|
|
67
|
+
- alternative_tools: Other compatible options
|
|
68
|
+
"""
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
ds_drivers_instructions = (
|
|
72
|
+
"""
|
|
73
|
+
Extract information systematically:
|
|
74
|
+
|
|
75
|
+
1. DRIVERS (Windows):
|
|
76
|
+
- download_url: Where to download drivers
|
|
77
|
+
- installation_steps: Step-by-step installation for Windows
|
|
78
|
+
Example:
|
|
79
|
+
1. Download from [URL]
|
|
80
|
+
2. Run installer
|
|
81
|
+
3. Connect debugger
|
|
82
|
+
4. Verify in Device Manager
|
|
83
|
+
|
|
84
|
+
2. DRIVERS (Linux):
|
|
85
|
+
- package_name: Package to install (if available)
|
|
86
|
+
- installation_steps: Commands to install
|
|
87
|
+
Example for J-Link:
|
|
88
|
+
1. Download from segger.com
|
|
89
|
+
2. sudo dpkg -i JLink_*.deb
|
|
90
|
+
3. Add udev rules
|
|
91
|
+
4. sudo usermod -a -G plugdev $USER
|
|
92
|
+
|
|
93
|
+
3. GDB SERVER:
|
|
94
|
+
- server_name: GDB server to use (JLinkGDBServer, OpenOCD, pyOCD, etc.)
|
|
95
|
+
- command_line: How to start server
|
|
96
|
+
Example: "JLinkGDBServer -device STM32F407VG -if SWD -speed 4000"
|
|
97
|
+
- config_file: Configuration if needed (OpenOCD .cfg)
|
|
98
|
+
- port: GDB port (default 3333)
|
|
99
|
+
"""
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
ds_vscode_launch_instructions = (
|
|
103
|
+
"""
|
|
104
|
+
Extract information systematically:
|
|
105
|
+
|
|
106
|
+
VS CODE LAUNCH.JSON:
|
|
107
|
+
- configuration_name: Name for the config (e.g., "Debug with J-Link")
|
|
108
|
+
- configuration_type: cortex-debug, cppdbg, etc.
|
|
109
|
+
- launch_json: Complete configuration object for .vscode/launch.json
|
|
110
|
+
- extensions_required: VS Code extensions needed
|
|
111
|
+
Common: "marus25.cortex-debug", "ms-vscode.cpptools"
|
|
112
|
+
- setup_notes: Any additional VS Code setup
|
|
113
|
+
"""
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
ds_add_tools_instructions = (
|
|
117
|
+
"""
|
|
118
|
+
Extract information systematically:
|
|
119
|
+
|
|
120
|
+
ADDITIONAL TOOLS:
|
|
121
|
+
- List helpful tools: OpenOCD, pyOCD, gdb-multiarch, etc.
|
|
122
|
+
"""
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
ds_troubleshooting_instructions = (
|
|
126
|
+
"""
|
|
127
|
+
Extract information systematically:
|
|
128
|
+
|
|
129
|
+
TROUBLESHOOTING:
|
|
130
|
+
- Common issues and solutions
|
|
131
|
+
- Connection problems
|
|
132
|
+
- Driver issues
|
|
133
|
+
- Permission issues (Linux)
|
|
134
|
+
"""
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
ds_quick_start_guide_instructions = (
|
|
138
|
+
"""
|
|
139
|
+
Extract information systematically:
|
|
140
|
+
|
|
141
|
+
QUICK START GUIDE:
|
|
142
|
+
Step-by-step instructions to get debugging working:
|
|
143
|
+
1. Install drivers
|
|
144
|
+
2. Connect debugger to target
|
|
145
|
+
3. Install VS Code extensions
|
|
146
|
+
4. Create/copy launch.json
|
|
147
|
+
5. Build project
|
|
148
|
+
6. Start debugging (F5)
|
|
149
|
+
"""
|
|
150
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Semaphore
|
|
3
|
+
from typing import Type, List, Dict
|
|
4
|
+
|
|
5
|
+
from google.genai.types import File
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from mfcli.client.gemini import Gemini
|
|
9
|
+
from mfcli.crud.functional_blocks import create_functional_blocks
|
|
10
|
+
from mfcli.models.functional_blocks import FunctionalBlocks, FunctionalBlockMeta
|
|
11
|
+
from mfcli.models.pipeline_run import PipelineRun
|
|
12
|
+
from mfcli.pipeline.analysis.generators.functional_blocks.instructions import (
|
|
13
|
+
fb_extractor_system_instructions,
|
|
14
|
+
fb_extractor_identify_instructions,
|
|
15
|
+
fb_extractor_fb_meta_instructions
|
|
16
|
+
)
|
|
17
|
+
from mfcli.pipeline.analysis.generators.functional_blocks.validator import validate_functional_blocks_against_netlist
|
|
18
|
+
from mfcli.utils.logger import get_logger
|
|
19
|
+
from mfcli.utils.orm import Session
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
FBExtractedData = FunctionalBlocks | FunctionalBlockMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FBCheatSheetGenerator:
|
|
27
|
+
def __init__(self, gemini: Gemini, files: List[File]):
|
|
28
|
+
self._gemini = gemini
|
|
29
|
+
self._sem = Semaphore(5)
|
|
30
|
+
self._files = files
|
|
31
|
+
|
|
32
|
+
async def _extract_fb_data(self, model: Type[BaseModel], instructions: str) -> FBExtractedData:
|
|
33
|
+
return await self._gemini.generate(
|
|
34
|
+
prompt=instructions,
|
|
35
|
+
instructions=fb_extractor_system_instructions,
|
|
36
|
+
response_model=model,
|
|
37
|
+
files=self._files
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
async def _retrieve_fb_list(self) -> list[str]:
|
|
41
|
+
blocks = await self._extract_fb_data(FunctionalBlocks, fb_extractor_identify_instructions)
|
|
42
|
+
return blocks.functional_blocks
|
|
43
|
+
|
|
44
|
+
async def _generate_fb_data(self, name: str) -> FunctionalBlockMeta:
|
|
45
|
+
block_instructions = fb_extractor_fb_meta_instructions.format(name)
|
|
46
|
+
return await self._extract_fb_data(FunctionalBlockMeta, block_instructions)
|
|
47
|
+
|
|
48
|
+
async def _generate_single_block(self, name: str) -> FunctionalBlockMeta:
|
|
49
|
+
async with self._sem:
|
|
50
|
+
return await self._generate_fb_data(name)
|
|
51
|
+
|
|
52
|
+
async def generate(self) -> List[FunctionalBlockMeta]:
|
|
53
|
+
blocks = await self._retrieve_fb_list()
|
|
54
|
+
tasks = []
|
|
55
|
+
for block in blocks:
|
|
56
|
+
tasks.append(self._generate_single_block(block))
|
|
57
|
+
responses = await asyncio.gather(*tasks)
|
|
58
|
+
block_data = []
|
|
59
|
+
for response in responses:
|
|
60
|
+
if isinstance(response, Exception):
|
|
61
|
+
continue
|
|
62
|
+
block_data.append(response)
|
|
63
|
+
return block_data
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_functional_blocks_dict(blocks: List[FunctionalBlockMeta]) -> Dict:
|
|
67
|
+
return {
|
|
68
|
+
"functional_blocks": [block.model_dump() for block in blocks]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def extract_functional_blocks_from_file(
|
|
73
|
+
db: Session,
|
|
74
|
+
pipeline_run: PipelineRun,
|
|
75
|
+
gemini: Gemini,
|
|
76
|
+
schematic_files: List[File] | None = None,
|
|
77
|
+
user_guide_files: List[File] | None = None,
|
|
78
|
+
netlist_file: File | None = None
|
|
79
|
+
) -> Dict:
|
|
80
|
+
if not schematic_files and not user_guide_files and not netlist_file:
|
|
81
|
+
error = "At least one of these file types must be specified: [schematic, user_guide, netlist]"
|
|
82
|
+
raise ValueError(error)
|
|
83
|
+
files = []
|
|
84
|
+
if schematic_files:
|
|
85
|
+
files += schematic_files
|
|
86
|
+
if user_guide_files:
|
|
87
|
+
files += user_guide_files
|
|
88
|
+
if netlist_file:
|
|
89
|
+
files.append(netlist_file)
|
|
90
|
+
blocks = await FBCheatSheetGenerator(gemini, files).generate()
|
|
91
|
+
validate_functional_blocks_against_netlist(db, pipeline_run.id, blocks)
|
|
92
|
+
create_functional_blocks(db, pipeline_run, blocks)
|
|
93
|
+
return _get_functional_blocks_dict(blocks)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
fb_extractor_system_instructions = (
|
|
2
|
+
"""
|
|
3
|
+
You are the Functional Block Generator agent. Your role is to analyze PDF schematics
|
|
4
|
+
and break them down into functional blocks that describe how different parts of the
|
|
5
|
+
circuit work together. This information helps firmware developers understand the
|
|
6
|
+
hardware and write code to support each function.
|
|
7
|
+
|
|
8
|
+
A functional block is a group of components that work together for a specific
|
|
9
|
+
purpose. Common examples include:
|
|
10
|
+
- Power Supply (voltage regulators, filtering capacitors)
|
|
11
|
+
- Microcontroller/MCU (main processor and its critical connections)
|
|
12
|
+
- Communication Interfaces (UART, SPI, I2C, USB, CAN, etc.)
|
|
13
|
+
- Sensor Interfaces (ADC inputs, sensor power, signal conditioning)
|
|
14
|
+
- Output Drivers (LED drivers, motor controllers, relay drivers)
|
|
15
|
+
- Debug/Programming Interface (JTAG, SWD, bootloader pins)
|
|
16
|
+
- External Connectors (headers, USB ports, JST connectors)
|
|
17
|
+
- Clock/Crystal circuits
|
|
18
|
+
- Reset circuits
|
|
19
|
+
- Protection circuits
|
|
20
|
+
"""
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
fb_extractor_identify_instructions = "Analyze the schematic to identify functional blocks"
|
|
24
|
+
|
|
25
|
+
fb_extractor_fb_meta_instructions = (
|
|
26
|
+
"""
|
|
27
|
+
Extract the following information for the {} functional block:
|
|
28
|
+
|
|
29
|
+
BLOCK IDENTIFICATION:
|
|
30
|
+
- block_name: Clear, descriptive name (e.g., "3.3V Power Supply", "USB Interface")
|
|
31
|
+
- description: What this block does and why it's important
|
|
32
|
+
- components: List all reference designators (R1, C1, U1, etc.) in this block
|
|
33
|
+
"""
|
|
34
|
+
)
|