diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..00ae61c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hello@devcrew.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..13be764 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +## Contributing + +[fork]: /fork +[pr]: /compare +[style]: https://standardjs.com/ +[code-of-conduct]: CODE_OF_CONDUCT.md + +Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. + +Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. + +## Issues and PRs + +If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. + +We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. + +## Submitting a pull request + +1. [Fork][fork] and clone the repository. +1. Create a new branch: `git checkout -b my-branch-name`. +1. Make your change, add tests, and make sure the tests still pass. +1. Push to your fork and [submit a pull request][pr]. +1. Pat your self on the back and wait for your pull request to be reviewed and merged. + +Here are a few things you can do that will increase the likelihood of your pull request being accepted: + +- Follow the [style guide][style] which is using standard. Any linting errors should be shown when running `npm test`. +- Write and update tests. +- Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. +- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. + +## Resources + +- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) +- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) +- [GitHub Help](https://help.github.com) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..7a91cdd --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,60 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '[BUG]' + +--- + +**Describe the Bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected Behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional Context** +Add any other context about the problem here. + +--- + +**Environment (please complete the following information):** +- OS: [e.g., Windows 10, macOS 11.5.1] +- Browser [e.g., Chrome, Firefox] +- Version [e.g., 22] + +--- + +**Optional: Proposed Solution** +If you have a solution in mind, please provide a clear and concise description. + +--- + +**Optional: Labels** +Add relevant labels to this issue (e.g., "bug", "high priority", "needs triage"). + +--- + +**Optional: Assignees** +Assign this issue to specific users responsible for addressing it. + +--- + +**Optional: Dependencies** +List any related issues, pull requests, or external dependencies. + +--- + +**Optional: Testing Steps** +Provide specific steps or instructions to test the fix. + +--- diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a5fb21b --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,54 @@ +--- +name: Pull Request +about: Submit a pull request to contribute to the project +title: '' + +--- + +**Description** +A clear and concise description of the changes proposed in this pull request. + +**Related Issue** +Closes #[issue number] + +**Motivation and Context** +Explain why these changes are necessary and provide any relevant background information. + +**Proposed Changes** +Describe the changes made in this pull request in detail. + +**Screenshots (if applicable)** +Add screenshots or GIFs to demonstrate the changes visually, if applicable. + +**Testing Steps** +Outline the steps to test the changes made in this pull request. + +**Additional Notes** +Add any additional information or notes that may be helpful for reviewers. + +--- + +**Checklist** +Please review and complete the following checklist before submitting the pull request: + +- [ ] I have read and followed the project's contributing guidelines. +- [ ] I have tested the changes locally and they are working as expected. +- [ ] I have added necessary documentation or updated existing documentation. +- [ ] I have added appropriate labels to this pull request. + +--- + +**Optional: Assignees** +Assign this pull request to specific users responsible for reviewing and merging it. + +--- + +**Optional: Reviewers** +Suggest specific users or teams to review this pull request. + +--- + +**Optional: Dependencies** +List any related pull requests, issues, or external dependencies. + +--- diff --git a/REPORT_ABUSE.md b/REPORT_ABUSE.md new file mode 100644 index 0000000..11d3abd --- /dev/null +++ b/REPORT_ABUSE.md @@ -0,0 +1,45 @@ +# Reporting Abuse Guidelines + +We are committed to providing a safe and inclusive environment for all contributors in our repository. If you witness or experience any form of abuse, harassment, or inappropriate behavior, we encourage you to report it. By reporting abuse, you help us maintain a respectful community and take appropriate actions. + +## Reporting Process + +To report abuse, please follow these steps: + +1. **Gather Information**: Collect any evidence or relevant details related to the incident, such as screenshots, links, or descriptions. + +2. **Choose Reporting Method**: + - Public Report: If you feel comfortable, you can report the abuse by creating a new issue in this repository. Make sure to use the appropriate issue template and provide as much information as possible. + - Private Report: If you prefer to report privately, you can reach out to our designated abuse reporting team via hello@devcrew.io. Your report will be handled confidentially. + +3. **Provide Details**: In your report, include the following information: + - Date and time of the incident. + - Description of the abusive behavior and any supporting evidence. + - Names or usernames of the individuals involved (if known). + +4. **Prompt Response**: We are committed to addressing abuse reports promptly. Our team will acknowledge receipt of your report within [specify timeframe] and initiate an investigation. + +## Investigation Process + +Once a report is received, we will: + +1. **Conduct Investigation**: Our team will conduct a thorough investigation into the reported abuse. We may reach out to the involved parties for additional information. + +2. **Maintain Confidentiality**: We will treat all reports and investigations with utmost confidentiality to protect the privacy and safety of individuals involved. + +3. **Take Appropriate Actions**: Based on the severity and nature of the abuse, we will take appropriate actions, which may include but are not limited to: + - Issuing a warning to the offender. + - Temporarily or permanently banning the offender from the repository. + - Escalating the matter to higher authorities or law enforcement if necessary. + +4. **Communication and Updates**: We will keep you informed of the progress and outcome of the investigation, ensuring transparency throughout the process. + +## Code of Conduct + +Our repository operates under the guidelines outlined in our Code of Conduct. We expect all contributors to adhere to these guidelines, which promote respect, inclusivity, and professionalism. You can find the Code of Conduct at [CODE_OF_CONDUCT](https://github.com/DevCrew-io/chatgpt-ios-sdk/blob/feat/fine-tune/CODE_OF_CONDUCT.md). + +## Support + +If you have any questions or need further assistance regarding the reporting process, please reach out to hello@devcrew.io. + +We appreciate your cooperation in helping us maintain a safe and respectful environment for everyone in our repository. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..96c3f15 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security + +## Reporting Potential Security Issues + +If you discover a potential security vulnerability in this project, +please notify us immediately by sending an email to hello@devcrew.io. +We appreciate your cooperation in helping us maintain the security and +integrity of our project. + +When reporting issues, please provide the following information: + +- Specify the affected component(s). +- Describe the steps to reproduce the issue. +- Summarize the security vulnerability and its potential impact. + +To protect our users and allow us to address the vulnerability before it +becomes public knowledge, we kindly request that you contact us through +the provided email address. This will enable us to release patches and updates +to safeguard our users' applications. + +## Policy + +Once a reported security vulnerability is verified, our policy is as follows: + +- We will promptly patch the current release branch and the immediate prior minor release branch. + +- We will release new security fix updates for each patched release branch without delay. + +Thank you for your collaboration in maintaining the security of our project and for helping us +create a safer environment for our users. diff --git a/Sources/ChatGPTAPIManager/ChatGPTAPIEndpoints.swift b/Sources/ChatGPTAPIManager/ChatGPTAPIEndpoints.swift index 44288f5..ebce0f5 100644 --- a/Sources/ChatGPTAPIManager/ChatGPTAPIEndpoints.swift +++ b/Sources/ChatGPTAPIManager/ChatGPTAPIEndpoints.swift @@ -37,17 +37,30 @@ enum ChatGPTAPIEndpoint { case retrievedModel(String) case moderations case embeddings + case files + case deleteFile(String) + case retrieveFile(String) + case retrieveFileContent(String) + case fineTunes + case retrieveFineTune(String) + case cancelFineTune(String) + case listFineTuneEvents(String) + case deleteFineTuneModel(String) + } extension ChatGPTAPIEndpoint { var method: String { switch self { - case .completion, .chat, .generateImage, .imageEdits, .imageVariations, .translations, .transcriptions, .moderations, .textEdit, .embeddings: + case .completion, .chat, .generateImage, .imageEdits, .imageVariations, .translations, .transcriptions, .moderations, .textEdit, .embeddings, .fineTunes, .cancelFineTune: return "POST" - - case .modelsList, .retrievedModel: + + case .modelsList, .retrievedModel, .files, .retrieveFile, .retrieveFileContent, .retrieveFineTune, .listFineTuneEvents: return "GET" + case.deleteFile, .deleteFineTuneModel: + return "DELETE" + } } @@ -77,6 +90,25 @@ extension ChatGPTAPIEndpoint { return URL(string: ChatGPTAPIEndpoint.baseURL + "/moderations")! case .embeddings: return URL(string: ChatGPTAPIEndpoint.baseURL + "/embeddings")! + case.files: + return URL(string: ChatGPTAPIEndpoint.baseURL + "/files")! + case.deleteFile(let fileID): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/files/\(fileID)")! + case.retrieveFile(let fileID): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/files/\(fileID)")! + case.retrieveFileContent(let fileID): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/files/\(fileID)/content")! + case.fineTunes: + return URL(string: ChatGPTAPIEndpoint.baseURL + "/fine-tunes")! + case.retrieveFineTune(let id): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/fine-tunes/\(id)")! + case.cancelFineTune(let id): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/fine-tunes/\(id)/cancel")! + case.listFineTuneEvents(let id): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/fine-tunes/\(id)/events")! + case.deleteFineTuneModel(let model): + return URL(string: ChatGPTAPIEndpoint.baseURL + "/models-tunes/\(model)")! + } } } diff --git a/Sources/ChatGPTAPIManager/ChatGPTAPIManager.swift b/Sources/ChatGPTAPIManager/ChatGPTAPIManager.swift index 68806d6..6145dd6 100644 --- a/Sources/ChatGPTAPIManager/ChatGPTAPIManager.swift +++ b/Sources/ChatGPTAPIManager/ChatGPTAPIManager.swift @@ -175,8 +175,562 @@ final public class ChatGPTAPIManager { self.embeddingsRequest(input: input, model: model, endPoint: .embeddings, completion: completion) } + + + /// Retrieves a list of files from the OpenAI API. + /// + /// - Parameter completion: A closure to be called when the API call is complete. It provides a `Result` object that represents either the retrieved data or an error. + public func getFilesList(completion: @escaping (Result) -> Void) { + self.getFiles(endPoint: .files, completion: completion) + } + + /// This function is used to make a file request using the provided parameters. + + /// - Parameters: + /// - fileUrl: The URL of the file to be requested. + /// - purpose: The purpose or description of the file request. + /// - endPoint: The endpoint or URL where the file request should be sent. + /// - completion: A closure that takes a Result type as input, indicating the success or failure of the file request operation. + + + public func fileRequest(fileUrl: URL, purpose: String, completion: @escaping (Result) -> Void) { + self.sendFileRequest(fileUrl: fileUrl, purpose: purpose, endPoint: .files, completion: completion) + } + + + /// Deletes a file with the specified file ID from the OpenAI API. + + /// - Parameters: + /// - fileID: The ID of the file to delete. + /// - completion: A completion handler called when the deletion is complete. The handler takes a `Result` object as its parameter. If the deletion is successful, the `Result` object will contain `Void`. If an error occurs, the `Result` object will contain the corresponding `Error`. + + + public func deleteFile(fileID: String, completion: @escaping (Result) -> Void) { + + let url = ChatGPTAPIEndpoint.deleteFile(fileID) + self.deleteFileRequest(endPoint: url, completion: completion) + } + + /// Retrieves file from the API. + /// + /// - Parameters: + /// - fileID: file id to retrieve + /// - completion: A completion handler that receives the result of the API request. + /// The result will either contain model of type `FilesModel` on success, + /// or an error on failure. + /// + + public func retrieveFile(fileID: String, completion: @escaping (Result) -> Void) { + self.retrieveFileRequest(endPoint: .retrieveFile(fileID), completion: completion) + } + + /// Retrieves file content from the API. + /// + /// - Parameters: + /// - fileID: file id to retrieve content + /// - completion: A completion handler that receives the result of the API request. + /// + /// + + public func retrieveFileContent(fileID: String, completion: @escaping (Result) -> Void) { + self.retrieveFileContentRequest(endPoint: .retrieveFileContent(fileID), completion: completion) + } + + /** + Fine-tunes a model using the specified training and validation files, along with other optional parameters. + + - Parameters: + - trainingFileID: The ID of the uploaded file containing the training data. + - validationFileID: The ID of the uploaded file containing the validation data (optional). + - model: The name of the base model to fine-tune (optional). Defaults to "curie". + - nEpochs: The number of epochs to train the model for (optional). Defaults to 4. + - batchSize: The batch size to use for training (optional). + - learningRateMultiplier: The learning rate multiplier to use for training (optional). + - promptLossWeight: The weight to use for loss on the prompt tokens (optional). + - computeClassificationMetrics: Specifies whether to calculate classification-specific metrics (optional). Defaults to `false`. + - classificationNClasses: The number of classes in a classification task (optional). + - classificationPositiveClass: The positive class in binary classification (optional). + - classificationBetas: The beta values for calculating F-beta scores (optional). + - suffix: A string to be added to the fine-tuned model name (optional). + - completion: A completion handler called when the fine-tuning is complete. The handler takes a `Result` object as its parameter. If the fine-tuning is successful, the `Result` object will contain `Void`. If an error occurs, the `Result` object will contain the corresponding `Error`. + */ + public func fineTuneModel(trainingFileID: String, + validationFileID: String?, + model: String?, + nEpochs: Int?, + batchSize: Int?, + learningRateMultiplier: Double?, + promptLossWeight: Double?, + computeClassificationMetrics: Bool?, + classificationNClasses: Int?, + classificationPositiveClass: String?, + classificationBetas: [Double]?, + suffix: String?, + completion: @escaping (Result) -> Void) { + self.fineTuneModelRequest(trainingFileID: trainingFileID, validationFileID: validationFileID, model: model, nEpochs: nEpochs, batchSize: batchSize, learningRateMultiplier: learningRateMultiplier, promptLossWeight: promptLossWeight, computeClassificationMetrics: computeClassificationMetrics, classificationNClasses: classificationNClasses, classificationPositiveClass: classificationPositiveClass, classificationBetas: classificationBetas, suffix: suffix, endPoint: .fineTunes, completion: completion) + } + + /// Retrieves a list of fine tunes from the OpenAI API. + /// + /// - Parameter completion: A closure to be called when the API call is complete. It provides a `Result` object that represents either the retrieved data or an error. + public func getFineTunesList(completion: @escaping (Result) -> Void) { + self.getFineTuneListRequest(endPoint: .fineTunes, completion: completion) + } + + /// Retrieves Fine Tune from the API. + /// + /// - Parameters: + /// - fineTuneID: Fine-Tune id to retrieve + /// - completion: A completion handler that receives the result of the API request. + /// The result will either contain model of type `FineTuneModel` on success, + /// or an error on failure. + /// + + public func retrieveFineTune(fineTuneID: String, completion: @escaping (Result) -> Void) { + self.retrieveFineTuneRequest(endPoint: .retrieveFineTune(fineTuneID), completion: completion) + } + + /// Immediately cancel a fine-tune job. + /// + /// - Parameters: + /// - fineTuneID: The ID of the fine-tune job to cancel + /// - completion: A completion handler that receives the result of the API request. + /// The result will either contain model of type `FineTuneModel` on success, + /// or an error on failure. + /// + + public func cancelFineTune(fineTuneID: String, completion: @escaping (Result) -> Void) { + self.cancelFineTuneRequest(endPoint: .cancelFineTune(fineTuneID), completion: completion) + } + + + + /// Retrieves fine-tune events for a specific fine-tune job. + /// - Parameters: + /// - fineTuneID: The ID of the fine-tune job to get events for. + /// - streaming: Whether to stream events for the fine-tune job. If set to `true`, events will be sent as data-only server-sent events as they become available. The stream will terminate with a `data: [DONE]` message when the job is finished (succeeded, cancelled, or failed). If set to `false`, only events generated so far will be returned. Default is `false`. + /// - completion: The completion handler that is called when the request is complete. It contains a `Result` object that represents either the retrieved data or an error if the request fails. + public func getFineTuneEvents(fineTuneID: String, streaming: Bool = false, completion: @escaping (Result) -> Void) { + + } + + + + /// Delete a fine-tuned model. You must have the Owner role in your organization + + /// - Parameters: + /// - model: The model to delete. + /// - completion: A completion handler called when the deletion is complete. The handler takes a `Result` object as its parameter. If the deletion is successful, the `Result` object will contain `Void`. If an error occurs, the `Result` object will contain the corresponding `Error`. + + public func deleteFineTune(model: String, completion: @escaping (Result) -> Void) { + + self.deleteFineTuneRequest(endPoint: .deleteFineTuneModel(model), completion: completion) + } // MARK: - Private Functions - + + private func fineTuneModelRequest(trainingFileID: String, + validationFileID: String?, + model: String?, + nEpochs: Int?, + batchSize: Int?, + learningRateMultiplier: Double?, + promptLossWeight: Double?, + computeClassificationMetrics: Bool?, + classificationNClasses: Int?, + classificationPositiveClass: String?, + classificationBetas: [Double]?, + suffix: String?, + endPoint:ChatGPTAPIEndpoint, + completion: @escaping (Result) -> Void) { + + + var parameters: [String: Any] = [ + "training_file": trainingFileID + ] + + if let validationFileID = validationFileID { + parameters["validation_file"] = validationFileID + } + + if let model = model { + parameters["model"] = model + } + + if let nEpochs = nEpochs { + parameters["n_epochs"] = nEpochs + } + + if let batchSize = batchSize { + parameters["batch_size"] = batchSize + } + + if let learningRateMultiplier = learningRateMultiplier { + parameters["learning_rate_multiplier"] = learningRateMultiplier + } + + if let promptLossWeight = promptLossWeight { + parameters["prompt_loss_weight"] = promptLossWeight + } + + if let computeClassificationMetrics = computeClassificationMetrics { + parameters["compute_classification_metrics"] = computeClassificationMetrics + } + + if let classificationNClasses = classificationNClasses { + parameters["classification_n_classes"] = classificationNClasses + } + + if let classificationPositiveClass = classificationPositiveClass { + parameters["classification_positive_class"] = classificationPositiveClass + } + + if let classificationBetas = classificationBetas { + parameters["classification_betas"] = classificationBetas + } + + if let suffix = suffix { + parameters["suffix"] = suffix + } + + let requestBuilder = DefaultRequestBuilder() + guard let request = requestBuilder.buildRequest(params: parameters, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + let parser = FineTuneParser() + parser.parseResponse(data: data, completion: { result in + + switch result { + case.success(let fineTuneObject): + completion(.success(fineTuneObject)) + + case.failure(let error): + completion(.failure(error)) + } + + }) + case.failure(let error): + completion(.failure(error)) + } + } + } + + private func getFineTuneListRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = GetRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FilesParser() + + parser.parseResponse(data: data, completion: { (result: Result) in + switch result { + case.success(let files): + completion(.success(files)) + case.failure(let error): + completion(.failure(error)) + } + }) + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func retrieveFineTuneRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = GetRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FilesParser() + parser.parseResponse(data: data, completion: { (result:Result) in + + switch result { + case.success(let files): + + completion(.success(files)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func cancelFineTuneRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = DefaultRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FilesParser() + parser.parseResponse(data: data, completion: { (result:Result) in + + switch result { + case.success(let files): + + completion(.success(files)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func fineTuneEventsRequest(endPoint: ChatGPTAPIEndpoint, streaming: Bool, completion: @escaping (Result) -> Void) { + + let requestBuilder = GetRequestWithQueryParameters() + var param: [String:Any] = ["stream":streaming] + guard let request = requestBuilder.buildRequest(params: param, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FilesParser() + parser.parseResponse(data: data, completion: { (result: Result) in + + switch result { + case.success(let events): + completion(.success(events)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func deleteFineTuneRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = DeleteRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = DeleteFineTuneParser() + parser.parseResponse(data: data, completion: { result in + + switch result { + case.success(let fileID): + print(fileID) + completion(.success(true)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + + private func deleteFileRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = DeleteRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FileParser() + parser.parseResponse(data: data, completion: { result in + + switch result { + case.success(let fileID): + print(fileID) + completion(.success(true)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func retrieveFileContentRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = GetRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + completion(.success(data)) + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func retrieveFileRequest(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + let requestBuilder = GetRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FileParser() + parser.parseResponse(data: data, completion: { result in + + switch result { + case.success(let files): + + completion(.success(files)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func getFiles(endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + + let requestBuilder = GetRequestBuilder() + guard let request = requestBuilder.buildRequest(params: nil, endPoint: endPoint, apiKey: apiKey) else { + completion(.failure(NetworkError.invalidURL)) + return + } + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + + let parser = FilesParser() + + parser.parseResponse(data: data, completion: { (result: Result) in + + switch result { + case.success(let files): + + completion(.success(files)) + + case.failure(let error): + completion(.failure(error)) + } + }) + + + case.failure(let error): + completion(.failure(error)) + } + + } + } + private func sendFileRequest(fileUrl: URL, purpose: String, endPoint: ChatGPTAPIEndpoint, completion: @escaping (Result) -> Void) { + + // Define the key-value pairs + let parameters: [String: Any] = [ + "purpose": purpose + ] + + let filenName = fileUrl.lastPathComponent + let fileData: Data + do { + fileData = try Data(contentsOf: fileUrl) + } catch { + completion(.failure(NetworkError.invalidURL)) + return + } + + guard let request = self.createMultiPartRequest(data: [fileData], fileNames: [filenName], params: parameters, name: "file", contentType: "application/octet-stream", endPoint: endPoint) else { + completion(.failure(NetworkError.invalidURL)) + return + } + + self.performDataTask(with: request) { result in + + switch result { + case.success(let data): + let parser = FileParser() + parser.parseResponse(data: data, completion: { result in + + switch result { + case.success(let fileObject): + + completion(.success(fileObject)) + + case.failure(let error): + completion(.failure(error)) + } + + }) + case.failure(let error): + completion(.failure(error)) + } + } + + } + private func textEditsRequest(endPoint: ChatGPTAPIEndpoint, model: EditGPTModels, input: String?, instruction: String, n: Int, temperature: Double, topP: Double, completion: @escaping (Result<[String],Error>) -> Void) { var parameters: [String: Any] = [ diff --git a/Sources/ChatGPTAPIManager/DataParser/AudioParser.swift b/Sources/ChatGPTAPIManager/DataParser/AudioParser.swift index f304637..1e627ef 100644 --- a/Sources/ChatGPTAPIManager/DataParser/AudioParser.swift +++ b/Sources/ChatGPTAPIManager/DataParser/AudioParser.swift @@ -10,7 +10,7 @@ class AudioParser: APIResponseParcer { /// Parse the response data for generating an image. /// - Parameter data: The response data from the API. - /// - Returns: The parsed image URL. + /// - Returns: The text. /// - Throws: An error if the response cannot be parsed. func parseResponse(data: Data,completion: @escaping(Result)->Void) { do { diff --git a/Sources/ChatGPTAPIManager/DataParser/FilesParser.swift b/Sources/ChatGPTAPIManager/DataParser/FilesParser.swift new file mode 100644 index 0000000..33f3675 --- /dev/null +++ b/Sources/ChatGPTAPIManager/DataParser/FilesParser.swift @@ -0,0 +1,86 @@ +// +// File.swift +// +// +// Created by Ghulam Abbas on 12/07/2023. +// + +import Foundation + +class FilesParser { + + func parseResponse(data: Data,completion: @escaping(Result)->Void) { + do { + + let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + debugPrint(responseJSON ?? []) + if let transcription = responseJSON?["data"] as? [[String:Any]] { + //debugPrint(transcription) + //completion(.success(transcription)) + do { + // Parse the JSON response into an array of type T + let models = try JSONDecoder().decode(T.self, from: data) + completion(.success(models)) + } catch { + // Error occurred during decoding, return failure in completion handler + completion(.failure(error)) + } + + } else { + if let error = responseJSON?["error"] as? [String:Any],let message = error["message"] as? String { + let error = NSError(domain: message, code: 401, userInfo: [ NSLocalizedDescriptionKey: message]) + + completion(.failure(error)) + return + } else { + completion(.failure(NetworkError.invalidResponse)) + return + } + } + + } catch (let error) { + completion(.failure(error)) + } + } +} + +class FileParser { + + /// Parse the response data for generating an image. + /// - Parameter data: The response data from the API. + /// - Returns: The parsed image URL. + /// - Throws: An error if the response cannot be parsed. + func parseResponse(data: Data,completion: @escaping(Result)->Void) { + do { + + let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + debugPrint(responseJSON ?? []) + if let status = responseJSON?["status"] as? String, status == "uploaded" || status == "processed" { + //debugPrint(transcription) + //completion(.success(transcription)) + do { + // Parse the JSON response into an array of type T + let models = try JSONDecoder().decode(FileModel.self, from: data) + completion(.success(models)) + } catch { + // Error occurred during decoding, return failure in completion handler + completion(.failure(error)) + } + + } else { + if let error = responseJSON?["error"] as? [String:Any],let message = error["message"] as? String { + let error = NSError(domain: message, code: 401, userInfo: [ NSLocalizedDescriptionKey: message]) + + completion(.failure(error)) + return + } else { + completion(.failure(NetworkError.invalidResponse)) + return + } + } + + } catch (let error) { + completion(.failure(error)) + } + } +} diff --git a/Sources/ChatGPTAPIManager/DataParser/FineTuneParser.swift b/Sources/ChatGPTAPIManager/DataParser/FineTuneParser.swift new file mode 100644 index 0000000..6b6d4f6 --- /dev/null +++ b/Sources/ChatGPTAPIManager/DataParser/FineTuneParser.swift @@ -0,0 +1,82 @@ +// +// File.swift +// +// +// Created by Ghulam Abbas on 12/07/2023. +// + +import Foundation + +class FineTuneParser { + + func parseResponse(data: Data,completion: @escaping(Result)->Void) { + do { + + let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + debugPrint(responseJSON ?? []) + if let transcription = responseJSON?["data"] as? [[String:Any]] { + //debugPrint(transcription) + //completion(.success(transcription)) + do { + // Parse the JSON response into an array of type T + let models = try JSONDecoder().decode(FineTuneModel.self, from: data) + completion(.success(models)) + } catch { + // Error occurred during decoding, return failure in completion handler + completion(.failure(error)) + } + + } else { + if let error = responseJSON?["error"] as? [String:Any],let message = error["message"] as? String { + let error = NSError(domain: message, code: 401, userInfo: [ NSLocalizedDescriptionKey: message]) + + completion(.failure(error)) + return + } else { + completion(.failure(NetworkError.invalidResponse)) + return + } + } + + } catch (let error) { + completion(.failure(error)) + } + } +} + +class DeleteFineTuneParser { + + func parseResponse(data: Data,completion: @escaping(Result)->Void) { + do { + + let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + debugPrint(responseJSON ?? []) + if let transcription = responseJSON?["data"] as? [[String:Any]] { + //debugPrint(transcription) + //completion(.success(transcription)) + do { + // Parse the JSON response into an array of type T + let models = try JSONDecoder().decode(DeleteFineTuneResponse.self, from: data) + completion(.success(models)) + } catch { + // Error occurred during decoding, return failure in completion handler + completion(.failure(error)) + } + + } else { + if let error = responseJSON?["error"] as? [String:Any],let message = error["message"] as? String { + let error = NSError(domain: message, code: 401, userInfo: [ NSLocalizedDescriptionKey: message]) + + completion(.failure(error)) + return + } else { + completion(.failure(NetworkError.invalidResponse)) + return + } + } + + } catch (let error) { + completion(.failure(error)) + } + } +} diff --git a/Sources/ChatGPTAPIManager/Models/FilesModel.swift b/Sources/ChatGPTAPIManager/Models/FilesModel.swift new file mode 100644 index 0000000..0954c9f --- /dev/null +++ b/Sources/ChatGPTAPIManager/Models/FilesModel.swift @@ -0,0 +1,25 @@ +// +// File.swift +// +// +// Created by Ghulam Abbas on 13/07/2023. +// + +import Foundation + +import Foundation + +public struct FilesModel: Codable { + + public let data: [FileModel] + public let object: String +} + +public struct FileModel: Codable { + public let id: String + public let object: String + public let bytes: Int + public let created_at: Int + public let filename: String + public let purpose: String +} diff --git a/Sources/ChatGPTAPIManager/Models/FineTuneModel.swift b/Sources/ChatGPTAPIManager/Models/FineTuneModel.swift new file mode 100644 index 0000000..03e06fa --- /dev/null +++ b/Sources/ChatGPTAPIManager/Models/FineTuneModel.swift @@ -0,0 +1,70 @@ +// +// File.swift +// +// +// Created by Ghulam Abbas on 12/07/2023. +// + +import Foundation + +public struct FineTuneModel: Codable { + public let id: String + public let object: String + public let model: String + public let created_at: Int + public let events: [FineTuneEventeModel]? + public let fine_tuned_model: String + public let hyperparams: HyperParamsModel? + public let organization_id: String + public let result_files: [ValidationFilesModel]? + public let status: String + public let validation_files: [ValidationFilesModel]? + public let training_files: [ValidationFilesModel]? + public let updated_at: Int +} +public struct FineTuneEventeModel: Codable { + public let message: String + public let object: String + public let level: String + public let created_at: Int +} + +public struct HyperParamsModel: Codable { + public let batch_size: Double + public let learning_rate_multiplier: String + public let n_epochs: Double + public let prompt_loss_weight: Double +} + +public struct ValidationFilesModel: Codable { + public let id: String + public let object: String + public let bytes: Int + public let created_at: Int + public let filename: String + public let purpose: String +} +public struct TrainingFilesModel: Codable { + public let id: String + public let object: String + public let bytes: Int + public let created_at: Int + public let filename: String + public let purpose: String +} + +public struct FineTuneResponse: Codable { + public let object: String + public let data: [FineTuneModel] +} + +public struct DeleteFineTuneResponse: Codable { + public let id: String + public let object: String + public let deleted: Bool +} + +public struct FineTuneEventsParser: Codable { + public let object: String + public let data: [FineTuneEventeModel] +} diff --git a/Sources/ChatGPTAPIManager/Utils/RequestBuilder.swift b/Sources/ChatGPTAPIManager/Utils/RequestBuilder.swift index 265ada5..5b546cd 100644 --- a/Sources/ChatGPTAPIManager/Utils/RequestBuilder.swift +++ b/Sources/ChatGPTAPIManager/Utils/RequestBuilder.swift @@ -49,6 +49,57 @@ struct GetRequestBuilder: RequestBuilder { } } +struct GetRequestWithQueryParameters: RequestBuilder { + func buildRequest(params: [String: Any]?, endPoint: ChatGPTAPIEndpoint?, apiKey: String) -> URLRequest? { + guard let endPoint = endPoint else { + return nil + } + + var components = URLComponents(url: endPoint.url, resolvingAgainstBaseURL: true) + + if let params = params { + var queryItems = [URLQueryItem]() + for (key, value) in params { + let stringValue: String + if let boolValue = value as? Bool { + stringValue = boolValue ? "true" : "false" + } else { + stringValue = "\(value)" + } + let queryItem = URLQueryItem(name: key, value: stringValue) + queryItems.append(queryItem) + } + components?.queryItems = queryItems + } + + guard let urlWithQuery = components?.url else { + return nil + } + + var request = URLRequest(url: urlWithQuery) + request.httpMethod = "GET" + request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } + +} +struct DeleteRequestBuilder: RequestBuilder { + func buildRequest(params: [String: Any]?, endPoint: ChatGPTAPIEndpoint?, apiKey: String) -> URLRequest? { + guard let endPoint = endPoint else { + return nil + } + + var request = URLRequest(url: endPoint.url) + request.httpMethod = "DELETE" + request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization") + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + return request + } +} + func createUrlRequest(requestBuilder: RequestBuilder, params: [String: Any]?, endPoint: ChatGPTAPIEndpoint?, apiKey: String) -> URLRequest? { return requestBuilder.buildRequest(params: params, endPoint: endPoint, apiKey: apiKey) }