diff --git a/pyproject.toml b/pyproject.toml index 39dba30..812bd00 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] # https://python-poetry.org/docs/pyproject/ name = "dspygen" -version = "2024.3.20" +version = "2024.3.22" description = "A Ruby on Rails style framework for the DSPy (Demonstrate, Search, Predict) project for Language Models like GPT, BERT, and LLama." authors = ["Sean Chatman "] readme = "README.md" diff --git a/src/dspygen/dsl/examples/example_pipeline.yaml b/src/dspygen/dsl/examples/example_pipeline.yaml index faa34f7..83335e3 100644 --- a/src/dspygen/dsl/examples/example_pipeline.yaml +++ b/src/dspygen/dsl/examples/example_pipeline.yaml @@ -1,77 +1,29 @@ lm_models: - label: "default" name: "OpenAI" - args: - model: "gpt-3.5-turbo" - max_tokens: 4096 - - label: "fast" - name: "OpenAI" - args: - model: "gpt-3.5-turbo" - max_tokens: 2048 - - label: "slow" - name: "T5Large" - args: - model: "fine-tuned-t5-large-1234" - max_tokens: 100 - -rm_models: - - label: "default" - name: "ColBERTv2" - - label: "product_features" - name: "prd_feats.csv" - - label: "user_feedback" - name: "db://user_feedback" - query: "SELECT * FROM user_feedback" signatures: - - name: "ProcessDataSignature" - docstring: "Processes raw data to synthesize into a structured format suitable for report generation." - inputs: - - name: "raw_data" - desc: "Raw data input that needs processing." - - name: "data_format" - desc: "The desired format of the output data." - outputs: - - name: "processed_data" - desc: "Data processed into a structured format." - - name: "GenerateReportSignature" - docstring: "Generates a comprehensive report from structured data." + - name: "GenerateGherkinSignature" + docstring: "Generates a comprehensive gherkin from structured data." inputs: - name: "processed_data" - desc: "Structured data to be included in the report." - - name: "report_template" - desc: "Template specifying the report's format and structure." + desc: "Structured data to be included in the gherkin." outputs: - - name: "report" - desc: "The final report generated from the structured data." + - name: "gherkin" + desc: "The final gherkin generated from the structured data." lm_modules: - - name: "DataProcessorModule" - signature: "ProcessDataSignature" - predictor: "Predict" - args: - - name: "raw_data" - value: "{{ user_input }}" - - name: "data_format" - value: "JSON" - - - name: "ReportGeneratorModule" - signature: "GenerateReportSignature" + - name: "GherkinGeneratorModule" + signature: "GenerateGherkinSignature" predictor: "ChainOfThought" args: - - name: "report_template" - value: "StandardReportTemplate" + - name: "gherkin_scenarios" + value: "StandardGherkinTemplate" steps: - - module: "DataProcessorModule" - lm_model: "default" - args: - raw_data: "id, name, age\n1, John, 25\n2, Jane, 30" - data_format: "YAML" + - file: "feature_list.yaml" - - module: "ReportGeneratorModule" + - module: "GherkinGeneratorModule" lm_model: "fast" args: - processed_data: "{{ processed_data }}" - report_template: "templates/standard_report.html" + processed_data: "{{ processed_data }}" \ No newline at end of file diff --git a/src/dspygen/dsl/utils/dsl_rm_module_utils.py b/src/dspygen/dsl/utils/dsl_rm_module_utils.py index 38c2705..1ec0c74 100644 --- a/src/dspygen/dsl/utils/dsl_rm_module_utils.py +++ b/src/dspygen/dsl/utils/dsl_rm_module_utils.py @@ -26,6 +26,9 @@ def _get_rm_module_instance(pipeline, rendered_args, step): Get the module instance for a given step from the top level definition or load the module. Uses the DSLModule class from dspygen.modules.dsl_module to handle modules defined in the pipeline YAML. """ - return DataRetriever(**rendered_args, pipeline=pipeline, step=step) + # Get the file_path from the pipeline.context or rendered_args + file_path = pipeline.context.get("file_path", rendered_args.get("file_path")) + + return DataRetriever(file_path=file_path, pipeline=pipeline, step=step, **rendered_args) diff --git a/src/dspygen/experiments/react_code_gen/__init__.py b/src/dspygen/experiments/react_code_gen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dspygen/experiments/react_code_gen/api-for-document-management.tsx b/src/dspygen/experiments/react_code_gen/api-for-document-management.tsx new file mode 100644 index 0000000..cc24679 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/api-for-document-management.tsx @@ -0,0 +1,96 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; + +interface Document { + id: string; + name: string; + url: string; +} + +const DocumentManagement: React.FC = () => { + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + setLoading(true); + axios + .get('/api/documents') + .then((res) => { + setDocuments(res.data); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); + + const uploadDocument = (file: File) => { + setLoading(true); + const formData = new FormData(); + formData.append('file', file); + axios + .post('/api/documents', formData) + .then((res) => { + setDocuments([...documents, res.data]); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + const downloadDocument = (id: string) => { + setLoading(true); + axios + .get(`/api/documents/${id}`) + .then((res) => { + const link = document.createElement('a'); + link.href = res.data.url; + link.download = res.data.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + const deleteDocument = (id: string) => { + setLoading(true); + axios + .delete(`/api/documents/${id}`) + .then(() => { + setDocuments(documents.filter((doc) => doc.id !== id)); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + return ( +
+ {loading &&

Loading...

} + {error &&

{error}

} + uploadDocument(e.target.files[0])} /> +
    + {documents.map((doc) => ( +
  • +

    {doc.name}

    + + +
  • + ))} +
+
+ ); +}; + +export default DocumentManagement; \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/confirm-signature-placement.tsx b/src/dspygen/experiments/react_code_gen/confirm-signature-placement.tsx new file mode 100644 index 0000000..72d2464 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/confirm-signature-placement.tsx @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; + +interface SignaturePlacementProps { + document: Document; +} + +const SignaturePlacement: React.FC = ({ document }) => { + const [signature, setSignature] = useState(null); + + const handleSignatureFieldClick = (event: React.MouseEvent) => { + const signatureField = event.currentTarget; + const signature = document.getSignature(signatureField.id); + setSignature(signature); + }; + + return ( +
+ + + {signature && } +
+ ); +}; + +export default SignaturePlacement; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/custom-signing-instructions.tsx b/src/dspygen/experiments/react_code_gen/custom-signing-instructions.tsx new file mode 100644 index 0000000..0db1d1f --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/custom-signing-instructions.tsx @@ -0,0 +1,22 @@ +import React, { useState } from 'react'; + +const CustomSigningInstructions = () => { + const [customInstructions, setCustomInstructions] = useState(''); + + const handleCustomInstructionsChange = (e: React.ChangeEvent) => { + setCustomInstructions(e.target.value); + }; + + const handleSaveCustomInstructions = () => { + // save custom instructions + }; + + return ( +
+ + +

{customInstructions}

+
+ ); +}; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/document-download.tsx b/src/dspygen/experiments/react_code_gen/document-download.tsx new file mode 100644 index 0000000..0bcbb8f --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/document-download.tsx @@ -0,0 +1,18 @@ +import React, { useState, useEffect } from 'react'; + +const DocumentDownload = () => { + const [downloaded, setDownloaded] = useState(false); + + useEffect(() => { + if (downloaded) { + // download document + } + }, [downloaded]); + + return ( +
+ +
+ ); +}; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/document-preview.tsx b/src/dspygen/experiments/react_code_gen/document-preview.tsx new file mode 100644 index 0000000..90fc868 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/document-preview.tsx @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; +import DocumentPreview from './DocumentPreview'; + +const DocumentPreviewPage = () => { + const [document, setDocument] = useState(null); + const [showPreview, setShowPreview] = useState(false); + const [showPrintOptions, setShowPrintOptions] = useState(false); + const [showShareOptions, setShowShareOptions] = useState(false); + const [showDownloadOptions, setShowDownloadOptions] = useState(false); + const [selectedShareOption, setSelectedShareOption] = useState(null); + const [selectedPrintOption, setSelectedPrintOption] = useState(null); + + const handleDocumentClick = () => { + setShowPreview(true); + }; + + const handlePreviewClose = () => { + setShowPreview(false); + }; + + const handlePrintClick = () => { + setShowPrintOptions(true); + }; + + const handlePrintOptionSelect = (option) => { + setSelectedPrintOption(option); + }; + + const handlePrint = () => { + // send document to printer with selected options + }; + + const handleShareClick = () => { + setShowShareOptions(true); + }; + + const handleShareOptionSelect = (option) => { + setSelectedShareOption(option); + }; + + const handleShare = () => { + // share document through selected option + }; + + const handleDownloadClick = () => { + setShowDownloadOptions(true); + }; + + const handleDownload = () => { + // download document to user's device + }; + + return ( +
+

Document Preview Page

+ + {showPreview && ( + + )} + {showPrintOptions && ( +
+

Select printing options:

+ + +
+ )} + {showShareOptions && ( +
+

Select sharing option:

+ + +
+ )} + {showDownloadOptions && ( +
+

Select download option:

+ + +
+ )} +
+ ); +}; + +export default DocumentPreviewPage; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/document-upload.tsx b/src/dspygen/experiments/react_code_gen/document-upload.tsx new file mode 100644 index 0000000..780ca29 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/document-upload.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; + +interface DocumentUploadProps { + onUpload: (file: File) => void; + onCancel: () => void; +} + +const DocumentUpload: React.FC = ({ onUpload, onCancel }) => { + const [files, setFiles] = useState([]); + const [error, setError] = useState(''); + + const handleFileChange = (e: React.ChangeEvent) => { + const fileList = e.target.files; + if (fileList) { + const filesArray = Array.from(fileList); + setFiles(filesArray); + } + }; + + const handleUpload = () => { + files.forEach((file) => { + if (file.type !== 'application/pdf') { + setError('Invalid file format'); + } else if (file.size > 1000000) { + setError('File size too large'); + } else { + onUpload(file); + } + }); + }; + + return ( +
+ + + + {error &&

{error}

} +
+ ); +}; + +export default DocumentUpload; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/drag-and-drop-document-upload.tsx b/src/dspygen/experiments/react_code_gen/drag-and-drop-document-upload.tsx new file mode 100644 index 0000000..73ae1f7 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/drag-and-drop-document-upload.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; + +const DragAndDropDocumentUpload = () => { + const [document, setDocument] = useState(null); + + const handleDrop = (e) => { + e.preventDefault(); + const file = e.dataTransfer.files[0]; + setDocument(file); + }; + + const handleDragOver = (e) => { + e.preventDefault(); + }; + + return ( +
+ {document &&

{document.name}

} +
+ ); +}; + +export default DragAndDropDocumentUpload; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/email-confirmation-to-sender.tsx b/src/dspygen/experiments/react_code_gen/email-confirmation-to-sender.tsx new file mode 100644 index 0000000..c0e9454 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/email-confirmation-to-sender.tsx @@ -0,0 +1,29 @@ +import React, { useState, useEffect } from 'react'; + +interface EmailConfirmationProps { + sender: string; + email: string; +} + +const EmailConfirmation: React.FC = ({ sender, email }) => { + const [confirmation, setConfirmation] = useState(false); + + useEffect(() => { + if (email) { + setConfirmation(true); + } + }, [email]); + + return ( +
+ {confirmation ? ( +

{`Email successfully delivered to ${sender}.`}

+ ) : ( +

{`Email failed to deliver to ${sender}.`}

+ )} +
+ ); +}; + +export default EmailConfirmation; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/email-link-to-signer.tsx b/src/dspygen/experiments/react_code_gen/email-link-to-signer.tsx new file mode 100644 index 0000000..9c797b5 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/email-link-to-signer.tsx @@ -0,0 +1,41 @@ +import React, { useState, useEffect } from 'react'; + +const EmailLinkToSigner = () => { + const [document, setDocument] = useState(null); + const [signer, setSigner] = useState(null); + const [emailSent, setEmailSent] = useState(false); + const [documentStatus, setDocumentStatus] = useState(null); + + useEffect(() => { + // fetch document data + // fetch signer data + }, []); + + const handleSendEmail = () => { + // send email to signer with link to document + setEmailSent(true); + }; + + const handleDocumentAccess = () => { + // update document status to "Sent" + setDocumentStatus("Sent"); + }; + + return ( +
+ + {emailSent && ( +
+

An email has been sent to {signer.name} with a link to the document.

+

The signer should receive the email within 24 hours.

+

The signer can access the document by clicking on the link in the email.

+ +
+ )} + {documentStatus === "Sent" &&

The document status has been updated to "Sent".

} +
+ ); +}; + +export default EmailLinkToSigner; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/feature_data_pipeline.yaml b/src/dspygen/experiments/react_code_gen/feature_data_pipeline.yaml new file mode 100644 index 0000000..3a5f347 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/feature_data_pipeline.yaml @@ -0,0 +1,8 @@ +# feature_data_pipeline.yaml +rm_models: + - label: "data_retriever" + name: "DataRetriever" + +steps: + - module: "FeatureDataModule" + rm_model: "data_retriever" diff --git a/src/dspygen/experiments/react_code_gen/generate-unique-signing-link.tsx b/src/dspygen/experiments/react_code_gen/generate-unique-signing-link.tsx new file mode 100644 index 0000000..4049b79 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/generate-unique-signing-link.tsx @@ -0,0 +1,42 @@ +import React, { useState, useEffect } from 'react'; + +interface Props { + name: string; + email: string; +} + +const SigningLinkGenerator: React.FC = ({ name, email }) => { + const [link, setLink] = useState(''); + const [error, setError] = useState(''); + + const generateLink = () => { + if (!name || !email) { + setError('Please enter a valid name and email'); + return; + } + + const link = `https://signinglink.com/${name}/${email}`; + setLink(link); + }; + + useEffect(() => { + const timer = setTimeout(() => { + setLink(''); + }, 86400000); + + return () => clearTimeout(timer); + }, []); + + return ( +
+ setName(e.target.value)} /> + setEmail(e.target.value)} /> + + {error &&

{error}

} + {link && Click here to sign} +
+ ); +}; + +export default SigningLinkGenerator; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/generate_react_code_from_csv.py b/src/dspygen/experiments/react_code_gen/generate_react_code_from_csv.py new file mode 100644 index 0000000..b2224b3 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/generate_react_code_from_csv.py @@ -0,0 +1,32 @@ +import os + +import inflection +from slugify import slugify + +from dspygen.dsl.dsl_pipeline_executor import execute_pipeline + + +def feature_code_generation(): + context = execute_pipeline(f'{os.getcwd()}/feature_data_pipeline.yaml', init_ctx={"file_path": f"{os.getcwd()}/features.csv"}) + + for result in context.data: + print(result) + context = execute_pipeline(f'{os.getcwd()}/gherkin_pipeline.yaml', init_ctx=context) + + file_name = slugify(f"{inflection.underscore(result['FeatureDescription'])}") + + with open(f"{file_name}.tsx", 'w') as f: + code = context.react_code + # remove trailing ``` if present + if code.endswith("```"): + code = code[:-3] + f.write(context.react_code) + print(f"React JSX code written to {file_name}") + + +def main(): + feature_code_generation() + + +if __name__ == '__main__': + main() diff --git a/src/dspygen/experiments/react_code_gen/gherkin_pipeline.yaml b/src/dspygen/experiments/react_code_gen/gherkin_pipeline.yaml new file mode 100644 index 0000000..73de26f --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/gherkin_pipeline.yaml @@ -0,0 +1,53 @@ +# gherkin_pipeline.yaml +lm_models: + - label: "default" + name: "OpenAI" + args: + max_tokens: 3000 + + - label: "smart" + name: "OpenAI" + args: + model: "gpt-4" + max_tokens: 6000 + +signatures: + - name: "GenerateGherkinSignature" + docstring: "Generates a comprehensive gherkin from structured data." + inputs: + - name: "data" + desc: "Structured data to be included in the gherkin." + outputs: + - name: "gherkin" + desc: "The final gherkin generated from the structured data." + + - name: "GenerateReactCodeSignature" + docstring: "Generates Typescript React code to enable the functionalities described in the provided Gherkin scenarios, with an emphasis on code quality." + inputs: + - name: "gherkin" + desc: "The Gherkin scenarios." + outputs: + - name: "react_code" + desc: "The generated React functionl component code with hooks. Write only one ```tsx block in the response." + prefix: "```tsx\n" + +lm_modules: + - name: "GherkinGeneratorModule" + signature: "GenerateGherkinSignature" + predictor: "ChainOfThought" + + - name: "ReactCodeGeneratorModule" + signature: "GenerateReactCodeSignature" + predictor: "Predictor" + + +steps: + - module: "GherkinGeneratorModule" + lm_model: "default" + args: + data: "{{ data }}" + + - module: "ReactCodeGeneratorModule" + lm_model: "default" + args: + gherkin: "{{ gherkin }}" \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/link-expiration.tsx b/src/dspygen/experiments/react_code_gen/link-expiration.tsx new file mode 100644 index 0000000..decaef4 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/link-expiration.tsx @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from 'react'; + +interface Props { + link: string; + expirationDate: Date; +} + +const LinkExpiration: React.FC = ({ link, expirationDate }) => { + const [isExpired, setIsExpired] = useState(false); + + useEffect(() => { + const currentDate = new Date(); + if (currentDate > expirationDate) { + setIsExpired(true); + } + }, [expirationDate]); + + return ( +
+ {isExpired ? ( +

The link is no longer accessible.

+ ) : ( + Link + )} +
+ ); +}; + +export default LinkExpiration; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/mobile-responsive-design.tsx b/src/dspygen/experiments/react_code_gen/mobile-responsive-design.tsx new file mode 100644 index 0000000..f89921b --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/mobile-responsive-design.tsx @@ -0,0 +1,83 @@ +import React, { useState, useEffect } from 'react'; +import { useMediaQuery } from 'react-responsive'; + +const MobileResponsiveDesign = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + }); + const isMobile = useMediaQuery({ query: '(max-width: 768px)' }); + + useEffect(() => { + if (isMobile) { + // adjust website to fit screen size + } + }, [isMobile]); + + const handleMenuClick = () => { + setIsMenuOpen(!isMenuOpen); + }; + + const handleFormChange = (e) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleFormSubmit = (e) => { + e.preventDefault(); + // submit form + }; + + return ( +
+ +
+

Website Content

+

Content goes here

+
+ + + + + + + +
+
+
+ ); +}; + +export default MobileResponsiveDesign; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/save-signature.tsx b/src/dspygen/experiments/react_code_gen/save-signature.tsx new file mode 100644 index 0000000..894a8ea --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/save-signature.tsx @@ -0,0 +1,21 @@ +import React, { useState } from 'react'; + +const SaveSignature = () => { + const [signature, setSignature] = useState(''); + + const handleSave = () => { + // save signature logic + setSignature('saved'); + }; + + return ( +
+

Signature Page

+ +

{signature}

+
+ ); +}; + +export default SaveSignature; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/signature-capture.tsx b/src/dspygen/experiments/react_code_gen/signature-capture.tsx new file mode 100644 index 0000000..f402514 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/signature-capture.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react'; + +interface SignatureCaptureProps { + onSave: (signature: string) => void; +} + +const SignatureCapture: React.FC = ({ onSave }) => { + const [signature, setSignature] = useState(''); + + const handleSave = () => { + onSave(signature); + }; + + return ( +
+

Signature Capture

+
+

Instructions

+

Sign your name in the box below.

+
+
+

Signature

+
+ setSignature(e.target.value)} + /> +
+
+
+ +
+
+ ); +}; + +export default SignatureCapture; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/react_code_gen/signature-validation.tsx b/src/dspygen/experiments/react_code_gen/signature-validation.tsx new file mode 100644 index 0000000..fd10f88 --- /dev/null +++ b/src/dspygen/experiments/react_code_gen/signature-validation.tsx @@ -0,0 +1,32 @@ +import React, { useState, useEffect } from 'react'; + +interface SignatureValidationProps { + document: Document; +} + +const SignatureValidation: React.FC = ({ document }) => { + const [signature, setSignature] = useState(null); + const [isValid, setIsValid] = useState(null); + + useEffect(() => { + setSignature(document.signature); + }, [document]); + + const validateSignature = () => { + if (signature) { + setIsValid(signature.isValid); + } + }; + + return ( +
+ + {isValid !== null && ( +

{isValid ? 'The signature is valid' : 'The signature is invalid'}

+ )} +
+ ); +}; + +export default SignatureValidation; +``` \ No newline at end of file diff --git a/src/dspygen/experiments/wip/default_pipeline.yaml b/src/dspygen/experiments/wip/default_pipeline.yaml new file mode 100644 index 0000000..d63aa8a --- /dev/null +++ b/src/dspygen/experiments/wip/default_pipeline.yaml @@ -0,0 +1,40 @@ +config: + current_step: + args: {} + lm_model: default + module: dspygen.dsl.dsl_dspy_module.DSLModule + rm_model: default + signature: default + global_signatures: {} +context: {} +lm_models: +- args: {} + label: default + name: OpenAI +lm_modules: +- name: default + predictor: Predict + signature: default +rm_models: +- args: {} + label: default + name: default +rm_modules: +- name: default +signatures: +- docstring: default + inputs: + - desc: default + name: default + prefix: '' + name: default + outputs: + - desc: default + name: default + prefix: '' +steps: +- args: {} + lm_model: default + module: dspygen.dsl.dsl_dspy_module.DSLModule + rm_model: default + signature: default diff --git a/src/dspygen/experiments/wip/one_shot_pipeline.py b/src/dspygen/experiments/wip/one_shot_pipeline.py index 939921f..c1412b8 100644 --- a/src/dspygen/experiments/wip/one_shot_pipeline.py +++ b/src/dspygen/experiments/wip/one_shot_pipeline.py @@ -2,11 +2,109 @@ from dspygen.dsl.dsl_pydantic_models import GenPipelineModel +REACT_CODE = """import React, { useState, useEffect } from 'react'; +import axios from 'axios'; + +interface Document { + id: string; + name: string; + url: string; +} + +const DocumentManagement: React.FC = () => { + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + setLoading(true); + axios + .get('/api/documents') + .then((res) => { + setDocuments(res.data); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); + + const uploadDocument = (file: File) => { + setLoading(true); + const formData = new FormData(); + formData.append('file', file); + axios + .post('/api/documents', formData) + .then((res) => { + setDocuments([...documents, res.data]); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + const downloadDocument = (id: string) => { + setLoading(true); + axios + .get(`/api/documents/${id}`) + .then((res) => { + const link = document.createElement('a'); + link.href = res.data.url; + link.download = res.data.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + const deleteDocument = (id: string) => { + setLoading(true); + axios + .delete(`/api/documents/${id}`) + .then(() => { + setDocuments(documents.filter((doc) => doc.id !== id)); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + + return ( +
+ {loading &&

Loading...

} + {error &&

{error}

} + uploadDocument(e.target.files[0])} /> +
    + {documents.map((doc) => ( +
  • +

    {doc.name}

    + + +
  • + ))} +
+
+ ); +}; + +export default DocumentManagement;""" + + def main(): from dspygen.utils.dspy_tools import init_dspy init_dspy(model="gpt-4", max_tokens=4000) - pipeline = GenPipelineModel.to_inst("3 step pipeline that creates a newsletter article from a user input. Be extremly verbose. Fill every value with salient details.") + pipeline = GenPipelineModel.to_inst(f"3 step pipeline that creates {REACT_CODE} . Be extremly verbose. Fill every value with salient details.") print(pipeline) diff --git a/src/dspygen/utils/file_tools.py b/src/dspygen/utils/file_tools.py index 7eecbac..b1a2c1f 100644 --- a/src/dspygen/utils/file_tools.py +++ b/src/dspygen/utils/file_tools.py @@ -28,6 +28,8 @@ def slugify(text): # Convert the text to lowercase text = text.lower() + text = text.replace(" ", "-") + # Replace spaces with hyphens and remove other non-alphanumeric characters text = re.sub(r"[^a-z0-9-]", "", text) diff --git a/src/dspygen/utils/pydantic_tools.py b/src/dspygen/utils/pydantic_tools.py index 839ff24..54c7068 100644 --- a/src/dspygen/utils/pydantic_tools.py +++ b/src/dspygen/utils/pydantic_tools.py @@ -1,5 +1,6 @@ from pydantic import BaseModel +from dspygen.experiments.gen_pydantic_instance import GenPydanticDict from dspygen.utils.yaml_tools import YAMLMixin @@ -9,8 +10,8 @@ def to_inst(cls, prompt): """ Turns the prompt into the instance of the Pydantic model. """ - # inst_dict = GenPydanticDict(model=cls)(prompt) - return {} # cls.model_validate(inst_dict) + inst_dict = GenPydanticDict(model=cls)(prompt) + return cls.model_validate(inst_dict) def main(): diff --git a/tests/dsl/data_hello_world_pipeline.yaml b/tests/dsl/data_hello_world_pipeline.yaml index 0874ddd..c7db757 100644 --- a/tests/dsl/data_hello_world_pipeline.yaml +++ b/tests/dsl/data_hello_world_pipeline.yaml @@ -6,5 +6,5 @@ steps: - module: "HelloWorldModule" rm_model: "data_retriever" args: - file_path: "{{ csv_file}}" + file_path: "{{ csv_file }}" return_columns: ["id", "name"] diff --git a/tests/dsl/gherkin_pipeline.yaml b/tests/dsl/gherkin_pipeline.yaml new file mode 100644 index 0000000..914e8c8 --- /dev/null +++ b/tests/dsl/gherkin_pipeline.yaml @@ -0,0 +1,46 @@ +lm_models: + - label: "default" + name: "OpenAI" + args: + max_tokens: 3000 + +signatures: + - name: "GenerateGherkinSignature" + docstring: "Generates a comprehensive gherkin from structured data." + inputs: + - name: "processed_data" + desc: "Structured data to be included in the gherkin." + outputs: + - name: "gherkin" + desc: "The final gherkin generated from the structured data." + + - name: "GenerateReactCodeSignature" + docstring: "Generates Typescript React code to enable the functionalities described in the provided Gherkin scenarios, with an emphasis on code quality." + inputs: + - name: "gherkin" + desc: "The Gherkin scenarios." + outputs: + - name: "react_code" + desc: "The generated React functionl component code with hooks." + prefix: "```tsx\n" + +lm_modules: + - name: "GherkinGeneratorModule" + signature: "GenerateGherkinSignature" + predictor: "ChainOfThought" + + - name: "ReactCodeGeneratorModule" + signature: "GenerateReactCodeSignature" + predictor: "Predictor" + + +steps: + - module: "GherkinGeneratorModule" + lm_model: "default" + args: + processed_data: "{{ processed_data }}" + + - module: "ReactCodeGeneratorModule" + lm_model: "default" + args: + gherkin: "{{ gherkin }}" \ No newline at end of file diff --git a/tests/dsl/test_prd_generator.py b/tests/dsl/test_prd_generator.py new file mode 100644 index 0000000..440c444 --- /dev/null +++ b/tests/dsl/test_prd_generator.py @@ -0,0 +1,90 @@ +from pprint import pprint + +import inflection +import pytest + +from dspygen.dsl.dsl_pipeline_executor import execute_pipeline +from dspygen.rm.data_retriever import DataRetriever + +FEATURE_CSV_CONTENT = """FeatureID,FeatureDescription +1,Document Upload +2,Generate Unique Signing Link +3,Email Link to Signer +4,Signature Capture +5,Document Preview +6,Save Signature +7,Confirm Signature Placement +8,Email Confirmation to Sender +9,Signature Validation +10,Document Download +11,Mobile Responsive Design +12,API for Document Management +13,Drag-and-Drop Document Upload +14,Custom Signing Instructions +15,Link Expiration +""" + + +@pytest.fixture +def sample_feature_csv_file(tmp_path): + # Create a temporary CSV file for features + file_path = tmp_path / "features.csv" + file_path.write_text(FEATURE_CSV_CONTENT) + return str(file_path) + + +def test_feature_data_retriever(sample_feature_csv_file): + # This example query is a placeholder. In a real scenario, you'd have specific logic to filter or process CSV data. + query = "SELECT * FROM df WHERE FeatureID <= 10" + return_columns = ['FeatureDescription'] + + # Initialize DataRetriever with the path to the temporary CSV file + data_retriever = DataRetriever(file_path=sample_feature_csv_file, return_columns=return_columns) + + # Execute the query using the forward method + filtered_results = data_retriever.forward(query=query) + + # Assertions to verify the results are as expected + expected_results = [ + {'FeatureDescription': 'Document Upload'}, + {'FeatureDescription': 'Generate Unique Signing Link'}, + ] + + assert len(filtered_results) == 10, "The filtered results did not match the expected number of features." + for expected, actual in zip(expected_results, filtered_results): + assert expected == actual, f"Expected {expected}, but got {actual}." + + +def test_feature_data_retriever(sample_feature_csv_file): + # This example query is a placeholder. In a real scenario, you'd have specific logic to filter or process CSV data. + query = "SELECT * FROM df WHERE FeatureID <= 10" + return_columns = ['FeatureDescription'] + + # Initialize DataRetriever with the path to the temporary CSV file + data_retriever = DataRetriever(file_path=sample_feature_csv_file, return_columns=return_columns) + + # Execute the query using the forward method + filtered_results = data_retriever.forward(query=query) + + +def test_feature_code_generation(sample_feature_csv_file): + # This example query is a placeholder. In a real scenario, you'd have specific logic to filter or process CSV data. + query = "SELECT * FROM df" + return_columns = ['FeatureDescription'] + + # Initialize DataRetriever with the path to the temporary CSV file + data_retriever = DataRetriever(file_path=sample_feature_csv_file, return_columns=return_columns) + + # Execute the query using the forward method + filtered_results = data_retriever.forward(query=query) + + for result in filtered_results: + print(result) + context = execute_pipeline('dsl/gherkin_pipeline.yaml', init_ctx={"processed_data": result}) + + file_name = f"{inflection.underscore(result['FeatureDescription'])}.tsx" + file_name = inflection.dasherize(file_name) + + with open(file_name, 'w') as f: + f.write(context.react_code) + print(f"React JSX code written to {file_name}")