Get Some Rest! A Full API Stack
Enhancing Web Scraping With Large Language Models: A Modern Approach
Modern API Management
When assessing prominent topics across DZone — and the software engineering space more broadly — it simply felt incomplete to conduct research on the larger impacts of data and the cloud without talking about such a crucial component of modern software architectures: APIs. Communication is key in an era when applications and data capabilities are growing increasingly complex. Therefore, we set our sights on investigating the emerging ways in which data that would otherwise be isolated can better integrate with and work alongside other app components and across systems.For DZone's 2024 Modern API Management Trend Report, we focused our research specifically on APIs' growing influence across domains, prevalent paradigms and implementation techniques, security strategies, AI, and automation. Alongside observations from our original research, practicing tech professionals from the DZone Community contributed articles addressing key topics in the API space, including automated API generation via no and low code; communication architecture design among systems, APIs, and microservices; GraphQL vs. REST; and the role of APIs in the modern cloud-native landscape.
Open Source Migration Practices and Patterns
MongoDB Essentials
Marty Cagan describes the job of the Product Manager as “to discover a product that is valuable, usable, and feasible." Finding the balance between the business, users, and technology demands a diverse skill set. There are many things that are going on simultaneously that require attention. In this regard, Jira is great. Sure, it has some downsides, but the tool can help Product Managers to: Keep the product strategy aligned. Clearly prioritize tasks while keeping them structured and organized. Analyze the performance of your team. Use a Roadmap To Keep Your Strategy Aligned As powerful as Jira is in the right hands, it is not a solution for everything. For instance, it is probably not the best tool for developing a product roadmap. However, it is quite good for managing one. What this means is that Jira has great functionality for managing roadmaps in a quick, actionable, and transparent way. Nevertheless, it requires proper input: You need to break down your scope into Epics and tasks before you start building a roadmap in Jira. We typically use a framework called BRIDGeS for multi-context analysis of a project. This framework leaves us with prioritized, ready-to-use Epics and tasks at the end of the session. Given this article is not about roadmaps per se, I would rather not go into too much detail. I will be focusing on Jira instead. Setting Up a Timeline in Jira Once you have your work broken down into Epics and tasks, creating a roadmap – or, as Jira calls it, a Timeline – is quite simple. Navigate to your board. Select the “Timeline” option from the menu on the right. Click on the “+ Create Epic” to add an Epic. Add child issues by clicking on the “+” sign next to the Epic. Click on the timeline to set the timeframe for the Epic. Tips and Tricks for Using Jira’s Timeline Feature Unlike most Jira features, the Timeline is rather intuitive and friendly to new users. Still, there are certain easily missable tips and tricks that can make your job much simpler. It’s just that you need to know where to look Add Dependencies You can add Dependencies between Epics from the Timeline. Simply hover over the timeline for an Epic and you will see two dots – one at the top right corner and one in the bottom left corner. Click and drag them to link one Epic to another. This is useful for understanding the order of work or visualizing potential blockers. Note: The color of the connective thread will change to red if the dates of Epics overlap. This feature is quite handy for easily seeing if certain dependencies are becoming blockers. Still, I’d recommend using dependencies wisely, otherwise the roadmap will become confusing because of the intertwined Epics. Use Different Colors for Epics You can right-click on the timeframe to easily change the color of an Epic or to remove start and end dates. Color-coding your Epics is a useful element of visualization. View Settings You can adjust the settings of the timeline if you wish to filter out certain completed issues or expand/collapse all of the Epics at the same time. Another useful option you can find in the view settings is the progress bar. Enable it to see a bar indicating the progress of an Epic. Filter out Epics With a Certain Status You can use the status category filter to hide the Epics and tasks that are marked as done from the timeline. This simple filter greatly improves the visibility of the roadmaps for times when you need to review done/in progress/future scope. Prioritize and Manage Tasks in the Backlog Now that we have an actionable plan, let’s take a look at how Jira can be used to execute it. Setting Up a Backlog in a Kanban Project In my experience, most Agile teams prefer to use a Scrum board that has the backlog feature enabled by default. That being said, a Kanban board needs a little bit of tweaking if you want to have a separate backlog rather than storing all of your issues on the board. The task of adding a backlog is slightly simpler for Team-Managed projects. Simply select the Add View option from the left side panel and enable the backlog. The process of adding the backlog in a Company-Managed project is a bit trickier. Go to the three dots menu at the top right corner of your board. Select Board settings. Select the Columns option. Drag the backlog status card from the board and into the Kanban backlog column. Delete the original Backlog column by clicking on the trash bin icon. Going back to the board, you’ll see that it has only three columns left, and the backlog has been moved to the side panel. Hint: This approach has an added benefit. Creating issues from the Backlog screen is much simpler and faster than from the board. Just click on the + Create Issue button and type in the name of your task. You can keep on typing and hitting enter to add new issues. And you can change their type as well. Setting Up a Backlog (Or Several) in a Scrum Project As I mentioned earlier, the Scrum project comes with the backlog feature enabled by default. That said, there is a major difference between the backlogs in Scrum and Kanban Jira projects: a Scrum Project has two backlogs by default. One is the Project Backlog and the other is the Sprint Backlog. The Sprint Backlog consists of a set of user stories or tasks that the development team commits to completing within a specific sprint or time-boxed iteration. It is a subset of the product backlog and represents the work sprint planning for that particular sprint. The Product Backlog contains a prioritized list of all the desired features, enhancements, and bug fixes for the product. It represents the complete scope of work that needs to be done over multiple sprints. Hint: The backlog view in Jira allows you to create several Sprints. These Sprints can be used as separate backlogs for certain specific tasks. For example, you can use these Sprints as separate backlogs for Bugs, Support Requests, the Icebox, etc. This functionality is super handy for keeping your work well-organized. Plus, this approach allows you to keep your work well-organized. The tasks from these backlogs can be pulled into the Sprint Backlog during the Sprint Planning Session. Story Points As a feature, Story Points are used to estimate the complexity of a user story. Typically, we use the following approach when it comes to assigning points to user stories: Point Description 1 One-liner change. You know what should be changed. Very easy to test. 2 You are aware of what to do. Changes are bigger than one-liner~1-2 days to implement. May include regression testing 3 Bigger scope. May require some research/documentation reading/codebase research. Includes unknown parts. 5 Biggest story. Not enough to split. 8 Must be split. Do research first. Bonus Tip: Backlog Refinement Backlog refinement is the process of reviewing, prioritizing, and tidying up the backlog. It is a necessary activity as, over time, people will add a lot of tasks that are missing context. For now, let’s focus on the benefits of tidying up your tasks: The team is working on the tasks that are adding real value to the product. The tasks are optimized and broken down in a way that a single issue doesn’t take longer than an entire Sprint. The work that is in progress reflects the roadmap. How do we do it? We typically refine the backlog once every two weeks. We take the stories from the product backlog and place them into relevant Sprint containers like Bugs, Technical Debt, or upcoming Sprint. We review the estimation and priority of the tasks that are being moved from the Product Backlog. Analyze the Performance of Your Team With the Built-In Reports Jira has a variety of reporting tools that are available to Product Managers. They are easily accessible from the reports tab on the right-side menu. Note: The Reports tab may not be enabled for you by default. Therefore, please follow these steps in case you do not see it: Select the Add View option. Select the More Features option. Find the Reports option and toggle it on. These reports can be used to analyze the performance of your team. They are also easily shareable and exportable. There is a wide selection of reports, but using all of them isn’t necessary. Here is a brief overview of several reports that we find to be the most useful: Burndown chart: Tracks the remaining story points in Jira and predicts the likelihood of completing the Sprint goal. Burnup chart: Tracks project progress over time and compares the work that is planned to the work that has been completed to date. Sprint report: Analyzes the work done during a Sprint. It is used to point out either overcommitment or scope creep in a Jira project. Velocity chart: This is a kind of bird’s eye view report that shows historical data of work completed from Sprint to Sprint. This chart is a nice tool for predicting how much work your team can reliably deliver based on previously burned Jira story points. Conclusion There are many new, slick work management tools on the market. Most are probably better than Jira in terms of UI and UX. That being said, as one of the oldest solutions out there, Jira has had the time and resources to develop a wide selection of features. This is why many PMs feel lost and confused when they are experiencing Jira for the first time. Don’t worry though – we’ve all been there. That’s why this little guide exists, showing you the different options of tools that will work best for you. Consider this to be your starting point in the endless sea of Jira features.
Kubernetes has pretty much become synonymous with container orchestration, and all competition is either assimilated (or rewritten to be based on Kubernetes, see Openshift), or basically disappeared into the void (sorry Nomad). Does that mean that development will slow down now? Or is it the beginning of something even bigger? Maybe Kubernetes is on the verge of becoming a generic name, just like Kleenex, or maybe a verb like “to google.” Years ago, somebody asked me in an interview what I think of Docker, and if I see a future for containerization. At the time my answer was quick and easy. First of all, containerization wasn’t a new concept. BSD, Solaris, as well as other systems, had them for years. They were kind of new to Linux though (at least in a widespread fashion), so they were here to stay. It was the next logical evolutionary step to virtualization. Docker, however, at least in my mind, was different. Towards Docker, I simply answered, “It’s the best tool we have today, but I hope it won’t be the final solution we can come up with.” While Docker turned around and is just coming back, the tooling we use today is unanimously built upon the specs defined by the open container initiative (OCI) and its OCI image format. So what will the future hold for Kubernetes? Is it going to stay or is it going to step into the abyss and will it “just be another platform that was replaced by something else,” as Michael Levan wrote in The Future of Kubernetes. The Rise of Kubernetes Jokingly, when looking up container orchestration in the dictionary, you’ll probably find the synonym Kubernetes, but it took Kubernetes about a decade to get to where it is today. Initially built by Google on the key learnings of Borg, Google’s internal container orchestration platform, Kubernetes was released in September 2014. By the release of Kubernetes, Borg itself was already over a decade old. By 2013 many of the original team members of the Borg started to look into the next step. Project 7 was born. At its release, Kubernetes was still using Docker underneath. A combination that most probably helped elevate Kubernetes' popularity. Docker was extremely popular at the time, but people started to find insufficiencies when trying to organize and run large numbers of containers. Kubernetes was about to fix it. With its concepts of building blocks, independently deployed and the actors (or agents) it was easy to extend but still understandable. In addition, resources are declarative by nature (written as JSON or YAML files), which enables version control of those definitions. Ever since the release, Kubernetes enabled more and more use cases, hence more companies started using it. I think a major step for the adoption was the release of Helm in 2016 which simplified the deployment process for more complex applications and enabled an “out of the box” experience. Now Kubernetes was “easy” (don’t quote me on easy though!). Today every cloud provider and their mothers offer a managed Kubernetes environment. Due to a set of standard interfaces, those services are mostly interchangeable. One of the big benefits of Kubernetes. Anyhow, it’s only mostly. The small inconsistencies and implementations or performance differences may give you quite the time of your life. Not a good one though. But let’s call it “run everywhere” because it mostly is. A great overview of the full history of Kubernetes, with all major milestones, can be found in The History of Kubernetes by Ferenc Hámori. Kubernetes, Love or Hate When we look into the community, opinions on Kubernetes diverge. Many people point out the internal (yet hidden) complexity of Kubernetes. A complexity that only increases with new features and additional functionality (also third-party) being added. This complexity is real, a reason why folks like Kelsey Hightower or Abdelfettah Sghiouar call Kubernetes a platform to build platforms (listen to our Cloud Commute podcast episode with Abdelfettah), meaning it should be used by the cloud providers (or company internal private cloud teams) to build a platform for container deployment, but it shouldn’t be used by the developers or just everyone. However, Kelsey also claimed that Kubernetes is a good place to start, not the endgame. Kelsey Hightower on Kubernetes being a platform to build platforms On the other end of the spectrum, you have people who refer to Kubernetes as the operating system of the cloud area. And due to the extensibility and feature richness, they’re probably not too far off. Modern operating systems have mostly one job, abstract away the underlying hardware and its features. That said, Kubernetes abstracts away many aspects of the cloud infrastructure and the operational processes necessary to run containers. In that sense, yes, Kubernetes is probably a Cloud OS. Especially since we started to see implementations of the Kubernetes running on operating systems other than Linux. Looking at you Microsoft. If you’re interested in learning more about the idea of Kubernetes as a Cloud OS, Natan Yellin from Robusta Dev wrote a very insightful article named Why Kubernetes will sustain the next 50 years. What Are the Next Steps? The most pressing question for Kubernetes as it stands today, is what will be next? Where will it evolve? Are we close to the end of the line? Looking back at Borg, a decade in, Google decided it was time to reiterate on orchestration and build upon the lessons learned. Kubernetes is about to hit its 10-year anniversary soon. So does that mean it’s time for another iteration? Many features in Kubernetes, such as secrets, were fine 10 years ago. Today we know that an encoded “secret” is certainly not enough. Temporary user accounts, OIDC, and similar technologies can and are integrated into Kubernetes already, increasing the complexity of it. Looking beyond Kubernetes, technology always runs in three phases, the beginning or adoption phase, the middle where everyone “has” to use it, and the end, where companies start to phase it out. Personally, I feel that Kubernetes is at its height right now, standing right in the middle. That doesn’t give us any prediction about the time frame for hitting the end though. At the moment it looks like Kubernetes will keep going and growing for a while. It doesn’t show any signs of slowing down. Other technologies, like micro virtual machines, using kata containers or Firecracker, are becoming more popular, offering higher isolation (hence security), but aren’t as efficient. The important element though, they offer a CRI-compatible interface. Meaning, they can be used as an alternative runtime underneath Kubernetes. In the not-too-distant future, I see Kubernetes offering multiple runtime environments, just as it offers multiple storage solutions today. Enabling running simple services in normal containers, but moving services with higher isolation needs to micro VMs. And there are other interesting developments, based on Kubernetes, too. Edgeless Systems implements a confidential computing solution, provided as a Kubernetes distribution named Constellation. Confidential computing makes use of CPU and GPU features that help to hardware-encrypt memory, not only for the whole system memory space, but per virtual machine, or even per container. That enables a whole set of new use cases, with end-to-end encryption for highly confidential calculations and data processes. While it’s possible to use it outside Kubernetes, the orchestration and operation benefits of running those calculations inside containers, make them easy to deploy and update. If you want to learn more about Constellation, we had Moritz Eckert from Edgeless Systems in our podcast not too long ago. Future or Fad? So, does Kubernetes have a bright future and will stand for the next 50 years, or will we realize that it is not what we’re looking for very soon-ish? If somebody would ask me today what I think about Kubernetes, I think I would answer similarly to my Docker answer. It is certainly the best tool we have today, making it the to-go container orchestration tool of today. Its ever-increasing complexity makes it hard to see the same in the future though. I think there are a lot of new lessons learned again. It’s probably time for a new iteration. Not today, not tomorrow, but somewhere in the next few years. Maybe this new iteration isn’t an all-new tool, but Kubernetes 2.0, who knows - but something has to change. Technology doesn’t stand still, the (container) world is different from what it was 10 years ago. If you asked somebody at the beginning of containerization, it was all about how containers have to be stateless, and what we do today. We deploy databases into Kubernetes, and we love it. Cloud-nativeness isn’t just stateless anymore, but I’d argue a good one-third of the container workloads may be stateful today (with ephemeral or persistent state), and it will keep increasing. The beauty of orchestration, automatic resource management, self-healing infrastructure, and everything in between is just too incredible to not use for “everything.” Anyhow, whatever happens to Kubernetes itself (maybe it will become an orchestration extension of the OCI?!), I think it will disappear from the eyes of the users. It (or its successor) will become the platform to build container runtime platforms. But to make that happen, debug features need to be made available. At the moment you have to look way too deep into Kubernetes or agent logs to find out and fix issues. The one who never had to find out why a Let’s Encrypt certificate isn’t updating may raise a hand now. To bring it to a close, Kubernetes certainly isn’t a fad, but I strongly hope it's not going to be our future either. At least not in its current incarnation.
Editor's Note: The following is an article written for and published in DZone's 2024 Trend Report, Modern API Management: Connecting Data-Driven Architectures Alongside AI, Automation, and Microservices. APIs play a pivotal role in the world of modern software development. Multiple types of APIs can be used to establish communication and data exchange between various systems. At the forefront lies the REST approach, which has dominated the industry due to its simplicity and scalability. However, as technology has evolved, the demands of developers and businesses have also changed. In recent years, alternatives such as GraphQL and asynchronous event-driven APIs have also emerged. They offer distinct advantages over traditional REST APIs. In this article, we will look into each of these API technologies and build a comparative understanding of them. REST: The Start of Resource-Oriented Communication REST architecture revolves around the concept of resources. These are entities that can be managed through standard HTTP methods such as GET, POST, PUT, and DELETE. One of the key characteristics of REST is its stateless nature, where each request from a client contains all the necessary information for the server to fulfill it. This decouples the client and server, allowing them to be scaled independently. Advantages and Disadvantages of REST REST APIs have some significant advantages: REST follows a simple and intuitive design based on standard HTTP methods. Each request in the REST approach is independent, resulting in better scalability and reliability. REST utilizes HTTP caching mechanisms to enhance performance and reduce the load on the origin server. REST is interoperable, working well with various programming languages and platforms due to its standard format. However, REST architecture also has several disadvantages: REST APIs can result in overfetching, where clients receive more data than needed, leading to inefficiency and waste of network bandwidth. Similar to the first point, REST APIs can also suffer from underfetching, where multiple requests are needed to fulfill complex data requirements. This results in increased latency. REST follows a synchronous approach that can lead to blocking and performance issues in high-load scenarios. Changes to the API's data schema can impact clients, resulting in tight coupling. Use Cases of REST APIs There are ideal use cases where REST APIs are much better suited when compared to other types of APIs, for example: Caching intensive applications – A read-heavy application, such as news websites or static content, can benefit from REST's caching mechanism. The standardized caching directives of REST make it easier to implement. Simple CRUD operations – When dealing with straightforward CRUD operations, REST APIs offer simplicity and predictability. Applications with a clear and static data model often find REST APIs to be more suitable. GraphQL: The Rise of Declarative Data Fetching With APIs GraphQL is a combination of an open-source language for querying data as well as a runtime for fulfilling those queries. The key principle behind GraphQL is to have a hierarchical structure for defining data queries, letting the clients precisely specify the data they need in a single request. Figure 1. GraphQL in the big picture In quite a few ways, GraphQL was a direct response to the issues with the traditional REST API architecture. However, it also promotes a strongly typed schema, offering developers a clear idea of what to expect. GraphQL supports real-time data updates through subscriptions. Over the years, a lot of work has happened on tools like GraphQL Federation to make GraphQL APIs more scalable for large enterprises with multiple domain areas. Advantages and Disadvantages of GraphQL GraphQL provides some key advantages: With GraphQL, clients can request only the specific data they need. This eliminates the overfetching and underfetching issues with REST APIs. GraphQL's strongly typed schema approach provides a clear structure and validation, speeding up development and documentation. GraphQL typically operates through a single endpoint. Clients just need to care about a single endpoint while talking to a GraphQL server even though there might be multiple sources for the data. Built-in introspection allows clients to explore the schema and discover available data and operations. There are also several disadvantages to GraphQL: Implementing GraphQL requires additional effort and expertise when compared to traditional REST APIs. Since the queries in GraphQL are flexible, caching of data can be challenging and may need custom solutions. While GraphQL reduces overfetching at the top level, nested queries can still lead to unnecessary data retrievals. Ownership of the common GraphQL layer becomes confusing, unlike the clear boundaries of a REST API. Use Cases of GraphQL There are specific scenarios where GraphQL does a better job as compared to REST APIs, for instance: Complex and nested data requirements – To fetch data with complex relationships, GraphQL helps clients precisely specify the data they need in a single query. Real-time data updates – GraphQL subscriptions help applications handle real-time data updates such as chat applications or live dashboards. With GraphQL, clients can subscribe to changes in specific data, allowing real-time updates without the need for frequent polling. Microservices architectures – In this case, data is distributed across multiple services. GraphQL provides a unified interface for clients to query data from various services. The client application doesn't have to manage multiple REST endpoints. Asynchronous APIs: A Shift to Event-Driven Architecture Over the years, the push to adopt, or migrate to, a cloud-native architecture has also given rise to event-driven architectures, the advantage being the prospect of non-blocking communication between components. With asynchronous APIs, clients don't need to wait for a response before proceeding further. They can send requests and continue their execution process. Such an approach is advantageous for scenarios that require high concurrency, scalability, and responsiveness. In event-driven systems, asynchronous APIs handle events and messages along with help from technologies like Apache Kafka and RabbitMQ, which offer a medium of communication between the message producer and the consumer. Considering a typical system using an event-driven API approach, we have producers publish events to topics, and consumers subscribe to these topics to receive and process the events asynchronously. This allows for seamless scalability and fault tolerance because both producers and consumers can evolve independently. The below diagram shows such a system: Figure 2. An event-driven system with Kafka and asynchronous APIs Advantages and Disadvantages of Asynchronous APIs There are some key advantages of asynchronous APIs: Asynchronous APIs are well suited for handling high concurrency and scalability requirements since multiple requests can be handled concurrently. Asynchronous APIs also enable real-time data processing by enabling timely response to events. Asynchronous APIs can also help better utilize system resources by offloading tasks to background processes. Lastly, asynchronous APIs increase the general fault tolerance of a system as one component failing doesn't disrupt the entire system. However, just like other API types, asynchronous APIs also have several disadvantages: There is increased complexity around message delivery, ordering, and error handling. Asynchronous APIs are more challenging to debug and test. Systems built using asynchronous APIs often result in eventual consistency, where data updates aren't immediately reflected across all components. Asynchronous APIs can also increase costs with regard to special systems for handling messages. Use Cases of Asynchronous APIs There are a few ideal use cases for asynchronous APIs when compared to REST and GraphQL APIs, including: Real-time data streaming – Asynchronous APIs are the best choice for real-time data streaming needs such as social media feeds, financial market updates, and IoT sensor data. These applications generate large volumes of data that need to be processed and delivered to clients in near real time. Integration with third-party systems – Asynchronous APIs are quite suitable for integrating with third-party systems that may have unpredictable response times or availability SLAs. Background tasks – Lastly, applications that require execution of background tasks — such as sending emails, notifications, or image/video processing — can benefit from the use of asynchronous APIs. Side-by-Side Comparison of REST, GraphQL, and Asynchronous APIs We've looked at all three types of API architectures. It is time to compare them side by side so that we can make better decisions about choosing one over the other. The table below shows this comparison across multiple parameters: Table 1. Comparing REST, GraphQL, and Async APIs Parameter REST APIs GraphQL APIs Asynchronous APIs Data fetching approach Data is fetched with predefined endpoints Clients specify the exact data requirements in the query Data is passed in the form of asynchronous messages Performance and scalability Highly suitable for scalable applications; can suffer from overfetching and underfetching problems Scalable; nested queries can be problematic Highly scalable; efficient for real-time data processing Flexibility and ease of use Limited flexibility in querying data High flexibility for querying data Limited flexibility in querying data and requires understanding of an event-driven approach Developer experience and learning curve Well established and familiar to many developers Moderate learning curve in terms of understanding the GraphQL syntax Steeper learning curve Real-time capabilities Limited real-time capabilities, relying on techniques like polling and webhooks for updates Real-time capabilities through subscriptions Designed for real-time data processing; highly suitable for streaming applications Tooling and ecosystem support Abundant tooling and ecosystem support Growing ecosystem The need for specialized tools such as messaging platforms like RabbitMQ or Kafka Conclusion In this article, we've explored the key distinctions between different API architectures: REST, GraphQL, and asynchronous APIs. We've also looked at scenarios where a particular type of API may be more suitable than others. Looking ahead, the API development landscape is poised for further transformation. Emerging technologies such as machine learning, edge computing, and IoT will drive new demands that necessitate the evolution of API approaches. Also, with the rapid growth of distributed systems, APIs will play a key role in enabling communication. As a developer, it's extremely important to understand the strengths and limitations of each API style and to select the approach that's most suitable for a given requirement. This mentality can help developers navigate the API landscape with confidence. This is an excerpt from DZone's 2024 Trend Report, Modern API Management: Connecting Data-Driven Architectures Alongside AI, Automation, and Microservices.Read the Free Report
Artificial intelligence (AI) has long been fascinated by large language models for their impressive capabilities. However, the recent emergence of smaller language models brings about a significant paradigm shift in AI development. These models, though compact, are highly efficient and offer scalability, accessibility, and efficiency to both developers and businesses. This article examines the transformative potential of smaller language models and their wide-ranging applications. Understanding Smaller Language Models Compact language models, often referred to as "lite" or "mini" models, are purposefully designed to achieve outstanding performance while requiring significantly fewer computational resources compared to their larger counterparts. This achievement is realized through the implementation of various techniques, including knowledge distillation, quantization, and pruning. Knowledge distillation involves transferring the expertise acquired by a larger model to a smaller one, typically by utilizing the outputs or internal representations of the larger model as targets for the smaller model to emulate. This process allows the smaller model to benefit from the knowledge and capabilities of its larger counterpart, despite its reduced size. Quantization, on the other hand, entails reducing the precision of the numerical values used to represent the weights and activations of a model. By converting these floating-point numbers into fixed-point numbers with fewer bits, quantization effectively reduces the memory footprint and computational complexity of the model, without significantly compromising its performance. Pruning, meanwhile, aims to simplify and compress the model by identifying and removing redundant connections (weights) between neurons. This process results in a more streamlined architecture that is smaller and more efficient, while ideally maintaining or even improving its performance. Together, these techniques enable compact language models to strike a delicate balance between size and functionality, making them an ideal solution for resource-restricted settings such as mobile applications and edge devices, where computational resources are limited. The Emergence of Small Language Models In the rapidly evolving field of artificial intelligence, the size of a language model has often been synonymous with its capability. While large language models (LLMs) like GPT-4 have dominated the AI landscape, smaller language models are now emerging as potent tools. This shift challenges the long-held notion that bigger is always better. Limitations of Large Language Models (LLMs) LLMs excel in areas like translation, summarization, and question-answering. However, their success comes at a cost: High energy consumption: LLMs require substantial computational resources. Memory requirements: They demand significant memory. Cost: Their computational costs can be prohibitive. GPU innovation lags behind the growing size of LLMs, hinting at a scaling ceiling. The Rise of Smaller Models Researchers are turning their attention to smaller language models due to their efficiency and versatility. Techniques like knowledge distillation from LLMs into smaller models yield similar performance with reduced computational demands. Transfer learning enables small models to effectively adapt to specific tasks by leveraging knowledge acquired from solving related problems. This approach has demonstrated its efficacy in fields like sentiment analysis and translation, where small language models can achieve comparable or superior results. For instance, consider a scenario where a small language model is initially trained on a large corpus of text data, such as Wikipedia articles or news articles. Following this pre-training phase, the model can undergo a process known as fine-tuning, where it is further trained on a smaller dataset specifically annotated for sentiment analysis or translation tasks. Through fine-tuning on these task-specific datasets, the model can learn to discern and extract pertinent features and patterns relevant to sentiment or translation. Consequently, this process enables the model to achieve outcomes that are on par with or surpass those obtained through training from scratch. Exploring Leading-Edge Small Language Models 1. DeepMind’s Chinchilla Insight Despite its smaller stature, DeepMind's Chinchilla is a formidable contender against larger models, challenging the conventional belief that size equates to superiority. Key Features Compact power: With 70 billion parameters, Chinchilla stands tall in performance. Data refinement: Fine-tuned on an extensive 1.4 trillion training tokens dataset Efficiency unveiled: Chinchilla's research delves into optimal training dataset size, model dimensions, and compute budget, emphasizing efficiency over sheer size. Safety and Ethics Its ongoing development underscores the paramount importance of safety and ethical considerations. (Ref) 2. Meta’s Llama Models Insight Meta's Llama models, ranging from 7B to 70B parameters, defy the notion that bigger is always better, excelling particularly in dialogue-based tasks. Fine-Tuning and Versatility Adaptable across various NLP applications, showcasing prowess from text generation to programming code (Ref) 3. Stanford’s Alpaca Insight Stanford's Alpaca, born from Meta AI’s LLaMa 7B model, demonstrates remarkable performance despite modest resources, targeting instruction-based tasks. Cautious Engagement Interaction with Alpaca demands caution due to ongoing development nuances. (Ref) 4. Stability AI’s StableLM Series Insight Stability AI's StableLM series unveils a harmonious blend of efficiency and effectiveness, offering impressive text generation capabilities. Performance Par Excellence StableLM 1.6B outshines larger counterparts, underscoring the triumph of efficiency. (Ref) Technological Advancements and Their Implications UL2R: Ultra Lightweight 2 Repair introduces a mixture-of-denoisers objective, enhancing performance across tasks. Flan: Fine-tuning models on tasks phrased as instructions improve both performance and usability. Applications Across Industries Natural Language Understanding (NLU) in IoT Devices Smaller language models revolutionize the functionality of IoT devices by enabling them to comprehend and respond to user queries efficiently. For instance, a smart home assistant equipped with a compact language model can understand commands such as "dim the lights" or "set the thermostat to 72 degrees" without relying heavily on cloud services. This allows for quicker response times and improved privacy for users. Example Consider a smart speaker integrated with a mini-language model. When a user asks, "What's the weather forecast for today?" the device processes the query locally and provides an immediate response based on the pre-trained knowledge within the model. This seamless interaction enhances user experience and reduces dependency on external servers. Personalized Content Recommendations Content recommendation systems driven by smaller language models offer personalized suggestions tailored to individual user preferences in real time. By analyzing browsing history, purchase behavior, and other relevant data, these models deliver accurate recommendations across various platforms. Example A streaming service utilizes a lite language model to analyze user viewing habits and preferences. Based on this data, the model suggests movies or TV shows that align with the user's interests. For instance, if a user frequently watches sci-fi movies, the recommendation system might suggest similar titles, enhancing user engagement and satisfaction. Medical Diagnosis and Healthcare In the healthcare sector, smaller language models assist medical professionals in tasks such as clinical documentation, diagnosis prediction, and drug interaction analysis. By processing medical texts efficiently, these models contribute to improved accuracy and decision-making, ultimately enhancing patient care. Example A healthcare application employs a mini-language model to assist doctors in diagnosing diseases based on symptoms provided by patients. The model analyzes the symptoms against a vast database of medical knowledge and offers potential diagnoses or treatment recommendations, aiding healthcare providers in delivering timely and accurate care. Educational Tools and Language Learning Language models tailored for educational purposes empower learners with personalized tutoring experiences, language translation, and grammar correction. These models support educators in creating interactive learning materials and adaptive assessment tools, fostering a more engaging and effective learning environment. Example A language learning app utilizes a compact language model to provide personalized feedback and exercises to users. The model identifies areas where the user may need improvement, such as grammar or vocabulary, and offers targeted exercises and explanations to enhance their language skills. This personalized approach accelerates the learning process and improves overall proficiency. Code Snippets Let’s explore sample code snippets for building smaller language models in Python. I’ll provide examples for N-gram language, Neural language, and Meta's Llama models. N-gram Language Model An N-gram language model is a statistical model used in natural language processing to predict the probability of a word given the previous N-1 words (or tokens) in a sequence of text. It works by analyzing the frequency of co-occurrences of sequences of N words, known as N-grams, within a corpus of text. Real-Life Use Case Consider a smartphone keyboard that suggests the next word while typing a message. This feature often utilizes an N-gram language model to predict the most probable next word based on the context of the preceding words in the sentence. For example, if the user types "I am going to" the model may predict "the" or "see" as the next word based on the frequency of occurrence of these phrases in the training data. Code Explanation In the provided Python code snippet, we demonstrate how to build a simple N-gram language model using Python: We start with a sample text, such as "I love reading blogs about data science on Analytics Vidhya." We tokenize the text into unigrams (individual words) using the split() function. Next, we create bigrams (pairs of consecutive words) by iterating over the list of unigrams. We then compute the probabilities of each bigram occurring in the text. For simplicity, we assume equal probabilities for each bigram. Finally, we demonstrate how to predict the probability of a specific bigram, such as "love reading", by querying the probabilities dictionary. This code snippet provides a basic illustration of how an N-gram language model can be implemented in Python to analyze text data and make predictions based on the observed patterns of word sequences. Python # Example: Building an N-gram Language Model # Sample text text = "I love reading blogs about data science on Analytics Vidhya." # Tokenize the text into unigrams (1-grams) unigrams = text.split() # Create bigrams (2-grams) bigrams = [(unigrams[i], unigrams[i + 1]) for i in range(len(unigrams) - 1)] # Compute probabilities (you can use frequency counts or other methods) # For simplicity, let's assume equal probabilities for each bigram probabilities = {bigram: 1 / len(bigrams) for bigram in bigrams} # Example: Predict the probability of the bigram "love reading" print(f"Probability of 'love reading': {probabilities.get(('love', 'reading'), 0)}") Neural Language Model A neural language model is a type of model in natural language processing (NLP) that uses neural networks to learn the patterns and relationships within a sequence of words. These models are capable of generating coherent and contextually relevant text, making them suitable for tasks such as language generation, machine translation, and text summarization. Real-Life Use Case Consider a virtual assistant, like Google Assistant or Siri, that responds to user queries with natural-sounding and contextually appropriate answers. These virtual assistants often utilize neural language models to understand and generate human-like responses based on the input received from users. Model Explanation In the provided Python code snippet, we demonstrate how to construct a neural language model using PyTorch and the Transformer architecture: We start by loading the WikiText2 dataset, which contains a large collection of English-language Wikipedia articles. We tokenize the raw text data using a basic English tokenizer. Next, we build a vocabulary from the tokenized data to convert words into numerical indices. We preprocess the raw text data by converting it into tensors suitable for training the neural network. We define the neural language model architecture, which in this case, is based on the Transformer architecture. The specifics of the model architecture, including the number of layers, hidden units, and attention mechanisms, can be adjusted based on the requirements of the task. We batchify the preprocessed data to facilitate efficient training of the model by dividing it into batches. Finally, we train the neural language model using the Transformer architecture, adjusting the model architecture, hyperparameters, and training loop as needed to optimize performance. This code snippet provides a foundational framework for building and training neural language models using PyTorch and the Transformer architecture, which can be further customized and extended for various NLP tasks and applications. Python import torch from torchtext.datasets import WikiText2 from torchtext.data.utils import get_tokenizer from torchtext.vocab import build_vocab_from_iterator # Load the WikiText2 dataset train_iter, val_iter, test_iter = WikiText2() tokenizer = get_tokenizer('basic_english') vocab = build_vocab_from_iterator(map(tokenizer, train_iter), specials=['<unk>']) vocab.set_default_index(vocab['<unk>']) # Convert raw text into tensors def data_process(raw_text_iter): data = [torch.tensor(vocab(tokenizer(item)), dtype=torch.long) for item in raw_text_iter] return torch.cat(tuple(filter(lambda t: t.numel() > 0, data))) train_data = data_process(train_iter) val_data = data_process(val_iter) test_data = data_process(test_iter) # Define your neural language model (e.g., using nn.Transformer) # Example: Batchify the data for training def batchify(data, bsz): nbatch = data.size(0) // bsz data = data.narrow(0, 0, nbatch * bsz) data = data.view(bsz, -1).t().contiguous() return data.to(device) batch_size = 32 train_data = batchify(train_data, batch_size) val_data = batchify(val_data, batch_size) test_data = batchify(test_data, batch_size) # Now you can train your neural language model using the Transformer architecture! # Remember to adjust the model architecture, hyperparameters, and training loop as needed. Meta’s Llama Models Meta’s Llama models are advanced language models specifically designed for fine-tuning and domain adaptation tasks. These models are part of the broader landscape of models provided by Meta AI, aimed at empowering developers with powerful natural language processing capabilities. Real-Life Use Case Consider a social media platform like Facebook, which utilizes Meta’s Llama models to enhance its content generation and recommendation systems. By fine-tuning the Llama models on the platform's vast amount of user-generated content, Meta can generate more relevant and engaging content recommendations tailored to individual users' preferences and interests. Model Explanation In the provided Python code snippet, we demonstrate how to utilize Meta’s Llama Models for text generation tasks: We start by installing the required packages, including PyTorch and the Transformers library. We then load the pre-trained LLaMa model and tokenizer provided by Meta AI. In this example, we're using the "llama-3B" variant of the LLaMa model. Next, we specify a prompt, which serves as the starting point for text generation. We encode the prompt using the LlamaTokenizer, converting it into input tokens suitable for feeding into the LLaMa model. We generate text using the LLaMa model by passing the encoded input tokens and specifying parameters such as the maximum length of the generated text and the number of sequences to generate. Finally, we decode the generated output tokens into human-readable text and print the generated text. This code snippet showcases how Meta’s Llama Models can be leveraged for text generation tasks, such as generating stories, captions, or responses, based on a given prompt. These models excel in capturing the nuances of natural language and producing coherent and contextually relevant text, making them valuable tools for a wide range of applications in NLP. Python # Install the required packages !pip install torch !pip install transformers import torch from transformers import LlamaForCausalLM, LlamaTokenizer # Load the pre-trained LLaMa model model_name = "meta-llama/llama-3B" tokenizer = LlamaTokenizer.from_pretrained(model_name) model = LlamaForCausalLM.from_pretrained(model_name) # Example: Generate text using the LLaMa model prompt = "Once upon a time" input_ids = tokenizer.encode(prompt, return_tensors="pt") output = model.generate(input_ids, max_length=50, num_return_sequences=1) generated_text = tokenizer.decode(output[0], skip_special_tokens=True) print("Generated text:", generated_text) Challenges and Opportunities Although smaller language models offer many benefits, there are also challenges to consider. Techniques used to compress these models may result in a loss of information or decreased performance, which requires careful optimization and fine-tuning. Additionally, ensuring that these models are deployed ethically and without bias is crucial to minimize the risks associated with algorithmic biases. Nevertheless, there is reason for optimism due to the rapid advancements in model compression algorithms and hardware optimization techniques. These advancements create significant opportunities for further innovation in this space. As the demand for AI-powered solutions continues to grow, the potential of smaller language models to democratize AI by making it more accessible and affordable across industries and regions is immense. Conclusion To summarize, the emergence of compact language models signifies a significant evolution in the field of AI, presenting an alluring substitute to conventional, extensive models. Their adaptability, efficacy, and expandability render them an ideal choice for a diverse array of applications spanning from edge computing to healthcare and education. With the potential of smaller language models, companies and developers can explore novel opportunities for advancement and simultaneously tackle the difficulties of resource limitations and ethical concerns in the implementation of AI.
Integrating assets from diverse platforms and ecosystems presents a significant challenge in enterprise application development, where projects often span multiple technologies and languages. Seamlessly incorporating web-based assets such as JavaScript, CSS, and other resources is a common yet complex requirement in Java web applications. The diversity of development ecosystems — each with its tools, package managers, and distribution methods — complicates including these assets in a unified development workflow. This fragmentation can lead to inefficiencies, increased development time, and potential for errors as developers navigate the intricacies of integrating disparate systems. Recognizing this challenge, the open-source project Npm2Mvn offers a solution to streamline the inclusion of NPM packages into Java workspaces, thereby bridging the gap between the JavaScript and Java ecosystems. Understanding NPM and Maven Before diving into the intricacies of Npm2Mvn, it's essential to understand the platforms it connects: NPM and Maven. NPM (Node Package Manager) is the default package manager for Node.js, primarily used for managing dependencies of various JavaScript projects. It hosts thousands of packages developers provide worldwide, facilitating the sharing and distribution of code. NPM simplifies adding, updating, and managing libraries and tools in your projects, making it an indispensable tool for JavaScript developers. Maven, on the other hand, is a powerful build automation tool used primarily for Java projects. It goes beyond simple build tasks by managing project dependencies, documentation, SCM (Source Code Management), and releases. Maven utilizes a Project Object Model (POM) file to manage a project's build configuration, dependencies, and other elements, ensuring developers can easily manage and build their Java applications. The Genesis of Npm2Mvn Npm2Mvn emerges as a solution to a familiar challenge developers face: incorporating the vast array of JavaScript libraries and frameworks available on NPM into Java projects. While Java and JavaScript operate in markedly different environments, the demand for utilizing web assets (like CSS, JavaScript files, and fonts) within Java applications has grown exponentially. It is particularly relevant for projects that require rich client interfaces or the server-side rendering of front-end components. Many Javascript projects are distributed exclusively through NPM, so like me, if you have found yourself copying and pasting assets from an NPM archive across to your Java web application workspace, then Npm2Mvn is just the solution you need. Key Features of Npm2Mvn Designed to automate the transformation of NPM packages into Maven-compatible jar files, Npm2Mvn makes NPM packages readily consumable by Java developers. This process involves several key steps: Standard Maven repository presentation: Utilizing another open-source project, uHTTPD, NPM2MVN presents itself as a standard Maven repository. Automatic package conversion: When a request for a Maven artifact in the group npm is received, NPM2MVN fetches the package metadata and tarball from NPM. It then enriches the package with additional metadata required for Maven, such as POM files and MANIFEST.MF. Inclusion of additional metadata: Besides standard Maven metadata, NPM2MVN adds specific metadata for Graal native images, enhancing compatibility and performance for projects leveraging GraalVM. Seamless integration into local Maven cache: The final jar file, enriched with the necessary metadata, is placed in the local Maven cache, just like any other artifact, ensuring that using NPM packages in Java projects is as straightforward as adding a Maven dependency. Benefits for Java Developers Npm2Mvn offers several compelling benefits for Java developers: Access to a vast repository of JavaScript libraries: By bridging NPM and Maven, Java developers can easily incorporate thousands of JavaScript libraries and frameworks into their projects. This access significantly expands the resources for enhancing Java applications, especially for UI/UX design, without leaving the familiar Maven ecosystem. Simplified dependency management: Managing dependencies across different ecosystems can be cumbersome. Npm2Mvn streamlines this process, allowing developers to handle NPM packages with the Maven commands they are accustomed to. Enhanced productivity: By automating the conversion of NPM packages to Maven artifacts, NPM2MVN saves developers considerable time and effort. This efficiency boost enables developers to focus more on building their applications than wrestling with package management intricacies. Real-world applications: Projects like Fontawesome, Xterm, and Bootstrap, staples for frontend development, can seamlessly integrate into Java applications. How To Use Using Npm2Mvn is straightforward. Jadaptive, the project's developers, host a repository here. This repository is open and free to use. You can also download a copy of the server to host in a private build environment. To use this service, add the repository entry to your POM file. XML <repositories> <repository> <id>npm2mvn</id> <url>https://npm2mvn.jadaptive.com</url> </repository> </repositories> Now, declare your NPM packages. For example, I am including the JQuery NPM package here. XML <dependency> <groupId>npm</groupId> <artifactId>jquery</artifactId> <version>3.7.1</version> </dependency> That's all we need to include and version manage NPM packages into the classpath. Consuming the NPM Resources in Your Java Application The resources of the NPM package are placed in the jar under a fixed prefix, allowing multiple versions of multiple NPM packages to be available to the JVM via the classpath or module path. For example, if the NPM package bootstrap@v5.3.1 contains a resource with the path css/bootstrap.css, then the Npm2Mvn package will make that resource available at the resource path /npm2mvn/npm/bootstrap/5.3.1/css/boostrap.css. Now that you know the path of the resources in your classpath, you can prepare to consume them in your Java web application by implementing a Servlet or other mechanism to serve the resources from the classpath. How you do this depends on your web application platform and any framework you use. In Spring Boot, we would add a resource handler as demonstrated below. Java @Configuration @EnableWebMvc public class MvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler("/npm2mvn/**") .addResourceLocations("classpath:/npm2mvn/"); } } With this configuration in a Spring Boot application, we can now reference NPM assets directly in HTML files we use in the application. HTML <script type="text/javascript" src="/npm2mvn/npm/jquery/3.7.1/dist/jquery.min.js"> But What About NPM Scopes? NPM version 2 supports scopes which, according to their website: ... allows you to create a package with the same name as a package created by another user or organization without conflict. In the examples above, we are not using scopes. If the package you require uses a scope, you must modify your pom.xml dependency and the resource path. Taking the FontAwesome project as an example, to include the @fortawesome/fontawesome-free module in our Maven build, we modify the groupId to include the scope as demonstrated below. XML <dependency> <groupId>npm.fortawesome</groupId> <artifactId>fontawesome-free</artifactId> <version>6.5.1</version> </dependency> Similarly, in the resource path, we change the second path value from 'npm' to the same groupId we used above. HTML <link rel="stylesheet" href="/npm2nvm/npm.fortawesome/fontawesome-free/6.5.1/css/all.css"/> You can download a full working Spring Boot example that integrates the Xterm NPM module and add-ons from GitHub. Dependency Generator The website at the hosted version of Npm2Mvn provides a useful utility that developers can use to get the correct syntax for the dependencies needed to build the artifacts. Here we have entered the scope, package, and version to get the correct dependency entry for the Maven build. If the project does not have a scope simply leave the first field blank. Conclusion Npm2Mvn bridges the JavaScript and Java worlds, enhancing developers' capabilities and project possibilities. By simplifying the integration of NPM packages into Java workspaces, Npm2Mvn promotes a more interconnected and efficient development environment. It empowers developers to leverage the best of both ecosystems in their applications.
Debugging is not just about identifying errors — it's about instituting a reliable process for ensuring software health and longevity. In this post, we discuss the role of software testing in debugging, including foundational concepts and how they converge to improve software quality. As a side note, if you like the content of this and the other posts in this series check out my Debugging book that covers this subject. If you have friends that are learning to code I'd appreciate a reference to my Java Basics book. If you want to get back to Java after a while check out my Java 8 to 21 book. The Intersection of Debugging and Testing Debugging and testing play distinct roles in software development. Debugging is the targeted process of identifying and fixing known bugs. Testing, on the other hand, encompasses an adjacent scope, identifying unknown issues by validating expected software behavior across a variety of scenarios. Both are a part of the debug fix cycle which is a core concept in debugging. Before we cover the cycle we should first make sure we're aligned on the basic terminology. Unit Tests Unit tests are tightly linked to debugging efforts, focusing on isolated parts of the application—typically individual functions or methods. Their purpose is to validate that each unit operates correctly in isolation, making them a swift and efficient tool in the debugging arsenal. These tests are characterized by their speed and consistency, enabling developers to run them frequently, sometimes even automatically as code is written within the IDE. Since software is so tightly bound it is nearly impossible to compose unit tests without extensive mocking. Mocking involves substituting a genuine component with a stand-in that returns predefined results, thus a test method can simulate scenarios without relying on the actual object. This is a powerful yet controversial tool. By using mocking we're in effect creating a synthetic environment that might misrepresent the real world. We're reducing the scope of the test which might perpetuate some bugs. Integration Tests Opposite to unit tests, integration tests examine the interactions between multiple units, providing a more comprehensive picture of the system's health. While they cover broader scenarios, their setup can be more complex due to the interactions involved. However, they are crucial in catching bugs that arise from the interplay between different software components. In general, mocking can be used in integration tests but it is discouraged. They take longer to run and are sometimes harder to set up. However, many developers (myself included) would argue that they are the only benchmark for quality. Most bugs express themselves in the seams between the modules and integration tests are better at detecting that. Since they are far more important some developers would argue that unit tests are unnecessary. This isn't true, unit test failures are much easier to read and understand. Since they are faster we can run them during development, even while typing. In that sense, the balance between the two approaches is the important part. Coverage Coverage is a metric that helps quantify the effectiveness of testing by indicating the proportion of code exercised by tests. It helps identify potential areas of the code that have not been tested, which could harbor undetected bugs. However, striving for 100% coverage can be a case of diminishing returns; the focus should remain on the quality and relevance of the tests rather than the metric itself. In my experience, chasing high coverage numbers often results in bad test practices that persist in problems. It is my opinion that unit tests should be excluded from coverage metrics due to the importance of integration tests to overall quality. To get a sense of quality coverage should focus on integration and end-to-end tests. The Debug-Fix Cycle The debug-fix cycle is a structured approach that integrates testing into the debugging process. The stages include identifying the bug, creating a test that reproduces the bug, fixing the bug, verifying the fix with the test, and finally, running the application to ensure the fix works in the live environment. This cycle emphasizes the importance of testing in not only identifying but also in preventing the recurrence of bugs. Notice that this is a simplified version of the cycle with a focus on the testing aspect only. The full cycle includes a discussion of the issue tracking and versioning as part of the whole process. I discuss this more in-depth in other posts in the series and my book. Composing Tests With Debuggers A powerful feature of using debuggers in test composition is their ability to "jump to line" or "set value." Developers can effectively reset the execution to a point before the test and rerun it with different conditions, without recompiling or rerunning the entire suite. This iterative process is invaluable for achieving desired test constraints and improves the quality of unit tests by refining the input parameters and expected outcomes. Increasing test coverage is about more than hitting a percentage; it's about ensuring that tests are meaningful and that they contribute to software quality. A debugger can significantly assist in this by identifying untested paths. When a test coverage tool highlights lines or conditions not reached by current tests, the debugger can be used to force execution down those paths. This helps in crafting additional tests that cover missed scenarios, ensuring that the coverage metric is not just a number but a true reflection of the software's tested state. In this case, you will notice that the next line in the body is a rejectValue call which will throw an exception. I don’t want an exception thrown as I still want to test all the permutations of the method. I can drag the execution pointer (arrow on the left) and place it back at the start of the method. Test-Driven Development How does all of this fit with disciplines like Test-Driven Development (TDD)? It doesn't fit well. Before we get into that let's revisit the basics of TDD. Weak TDD typically means just writing tests before writing the code. Strong TDD involves a red-green-refactor cycle: Red: Write a test that fails because the feature it tests isn't implemented yet. Green: Write the minimum amount of code necessary to make the test pass. Refactor: Clean up the code while ensuring that tests continue to pass. This rigorous cycle guarantees that new code is continually tested and refactored, reducing the likelihood of complex bugs. It also means that when bugs do appear, they are often easier to isolate and fix due to the modular and well-tested nature of the codebase. At least, that's the theory. TDD can be especially advantageous for scripting and loosely typed languages. In environments lacking the rigid structure of compilers and linters, TDD steps in to provide the necessary checks that would otherwise be performed during compilation in statically typed languages. It becomes a crucial substitute for compiler/linter checks, ensuring that type and logic errors are caught early. In real-world application development, TDD's utility is nuanced. While it encourages thorough testing and upfront design, it can sometimes hinder the natural flow of development, especially in complex systems that evolve through numerous iterations. The requirement for 100% test coverage can lead to an unnecessary focus on fulfilling metrics rather than writing meaningful tests. The biggest problem in TDD is its focus on unit testing. TDD is impractical with integration tests as the process would take too long. But as we determined at the start of this post, integration tests are the true benchmark for quality. In that test TDD is a methodology that provides great quality for arbitrary tests, but not necessarily great quality for the final product. You might have the best cog in the world, but if doesn't fit well into the machine then it isn't great. Final Word Debugging is a tool that not only fixes bugs but also actively aids in crafting tests that bolster software quality. By utilizing debuggers in test composition and increasing coverage, developers can create a suite of tests that not only identifies existing issues but also guards against future ones, thus ensuring the delivery of reliable, high-quality software. Debugging lets us increase coverage and verify edge cases effectively. It's part of a standardized process for issue resolution that's critical for reliability and prevents regressions.
In the dynamic landscape of artificial intelligence (AI), two groundbreaking technologies — Large Language Models (LLM) and Retrieval-Augmented Generation (RAG) — stand out for their transformative potential in understanding and generating human-like text. This article embarks on a comparative journey between LLM and RAG, shedding light on their mechanisms, applications, and the unique advantages they offer to the AI field. Large Language Models (LLM): Foundations and Applications LLMs, such as GPT (Generative Pre-trained Transformer), have revolutionized the AI scene with their ability to generate coherent and contextually relevant text across a wide array of topics. At their core, LLMs rely on vast amounts of text data and sophisticated neural network architectures to learn language patterns, grammar, and knowledge from the textual content they have been trained on. The strength of LLMs lies in their generalization capabilities: they can perform a variety of language-related tasks without task-specific training. This includes translating languages, answering questions, and even writing articles. However, LLMs are not without their challenges. They sometimes generate plausible-sounding but incorrect or nonsensical answers, a phenomenon known as a "hallucination." Additionally, the quality of their output heavily depends on the quality and breadth of their training data. Core Aspects Scale: The hallmark of LLMs is their vast parameter count, reaching into the billions, which captures a wide linguistic range. Training regime: They undergo pre-training on diverse text data, subsequently fine-tuned for tailored tasks, embedding a deep understanding of language nuances. Utility spectrum: LLMs find their use across various fronts, from aiding in content creation to facilitating language translation. Example: Generating Text With an LLM To illustrate, consider the following Python code snippet that uses an LLM to generate a text sample: Python from transformers import GPT2Tokenizer, GPT2LMHeadModel # Input prompt = "How long have Australia held on to the Ashes?" # Encode the inputs with GPT2 Tokenizer tokenizer = GPT2Tokenizer.from_pretrained('gpt2') inputs = tokenizer.encode(prompt, return_tensors='pt') ## using pyTorch ('tf' to use TensorFlow) # Generate outputs with gpt2 Model model = GPT2LMHeadModel.from_pretrained('gpt2') outputs = model.generate(inputs, max_length=25) # Decode and print the result result = tokenizer.decode(outputs[0], skip_special_tokens=True) print("Generated text:", result) This code initializes a text generation pipeline using GPT-2, a popular LLM, and generates text based on a given prompt. Retrieval-Augmented Generation (RAG): An Overview and Use Cases RAG introduces a novel approach by combining the generative capabilities of models like GPT with a retrieval mechanism. This mechanism searches a database of text (such as Wikipedia) in real time to find relevant information that can be used to inform the model's responses. This blending of retrieval and generation allows RAG to produce answers that are not only contextually relevant but also grounded in factual information. One of the main advantages of RAG over traditional LLMs is its ability to provide more accurate and specific information by referencing up-to-date sources. This makes RAG particularly useful for applications where accuracy and timeliness of information are critical, such as in news reporting or academic research assistance. However, the reliance on external databases means that RAG's performance can suffer if the database is not comprehensive or if the retrieval process is inefficient. Furthermore, integrating retrieval mechanisms into the generative process adds complexity to the model, potentially increasing the computational resources required. Core Aspects Hybrid nature: RAG models first retrieve pertinent documents, and then utilize this context for informed generation. Dynamic knowledge access: Unlike LLMs, RAG models can tap into the latest or domain-specific data, offering enhanced versatility. Application areas: RAG shines in scenarios demanding external knowledge, such as in-depth question answering and factual content generation. Example: Implementing RAG for Information Retrieval Below is a simplified example of how one might implement a basic RAG system for retrieving and generating text: Python from transformers import RagTokenizer, RagRetriever, RagSequenceForGeneration # A sample query to ask the model query = "How long have Australia held on to the Ashes?" tokenizer = RagTokenizer.from_pretrained("facebook/rag-sequence-nq") ## Get the tokenizer from the pretrained model tokenized_text = tokenizer(query, return_tensors='pt', max_length=100, truncation=True) ## Encode/Tokenize the query # Find results with RAG-Sequence model (uncased model) using wiki_dpr dataset retriever = RagRetriever.from_pretrained("facebook/rag-sequence-nq", index_name="exact", use_dummy_dataset=True) ## Uses a pretrained DPR dataset (wiki_dpr) https://huggingface.co/datasets/wiki_dpr model = RagSequenceForGeneration.from_pretrained("facebook/rag-sequence-nq", retriever=retriever) model_generated_tokens = model.generate(input_ids=tokenized_text["input_ids"], max_new_tokens=1000) ## Find the relavant information from the dataset (tokens) print(tokenizer.batch_decode(model_generated_tokens, skip_special_tokens=True)[0]) ## Decode the data to find the answer This code utilizes Facebook's RAG model to answer a query by first tokenizing the input and then generating a response based on information retrieved in real time. Comparative Insights: LLM vs RAG The choice between LLM and RAG hinges on specific task requirements. Here’s how they stack up: Knowledge Accessibility LLMs rely on their pre-training corpus, possibly leading to outdated information. RAG, with its retrieval capability, ensures access to the most current data. Implementation Complexity RAG models, owing to their dual-step nature, present a higher complexity and necessitate more resources than LLMs. Flexibility and Application Both model types offer broad application potential. LLMs serve as a robust foundation for varied NLP tasks, while RAG models excel where instant access to external, detailed data is paramount. Conclusion: Navigating the LLM and RAG Landscape Both LLM and RAG represent significant strides in AI's capability to understand and generate human-like text. Selecting between LLM and RAG models involves weighing the unique demands of your NLP project. LLMs offer versatility and generalization, making them suitable for a wide range of applications and a go-to for diverse language tasks. In contrast, RAG's strength lies in its ability to provide accurate, information-rich responses, particularly valuable in knowledge-intensive tasks and ideal for situations where the incorporation of the latest or specific detailed information is crucial. As AI continues to evolve, the comparative analysis of LLM and RAG underscores the importance of selecting the right tool for the right task. Developers and researchers are encouraged to weigh these technologies' benefits and limitations in the context of their specific needs, aiming to leverage AI's full potential in creating intelligent, responsive, and context-aware applications.
APIs and SDKs are the bridge to an underlying platform, allowing firms to build applications and integrate your platform into their business processes. Building APIs and SDKs that developers love to use is the key to a successful platform strategy, be it for internal teams or external teams. In the following article, I will provide some of the most effective practices I have seen in the industry. I place these four necessary strategies that should be at the heart of any API/SDK program: simplicity, resilience, community building, and continuous improvement. Prioritize Simplicity Simplicity is the most essential factor to consider while designing APIs and SDKs. Firms are more likely to adopt and stay with you if API and SDK usage is intuitive, well-documented, and easy to plug into other projects. Do not over-engineer or overcomplicate APIs/SDKs. Preferring clarity, consistency, and compliance with industry standards, draft intuitive and user-friendly APIs and SDKs. Create endpoints with concise, descriptive naming best practices that accurately convey their purpose. Codify in-house standards on your entire API or SDK, with appropriate naming conventions and design patterns. Align with widely adopted standards and paradigms, such as RESTful principles, appropriate HTTP methods, language-specific conventions, and secure authentication mechanisms, to provide a seamless and familiar experience for developers. Here are a few good examples to consider: API Design Guidelines APIs Design API Standards Style Guide Blindly following a style guide without considering the unique requirements and goals of your platform can lead to suboptimal outcomes. It is important to strike a balance between catering to developers' needs and doing what's right for the long-term success and viability of your platform. While it might be tempting to fulfill every feature request from your users, you must make hard choices to prioritize the health and maintainability of your platform (the adage applies: "Put on your own oxygen mask first before assisting others"). Nothing erodes trust like a platform that lacks stability, and security or cannot scale, so work hard to find the right balance between a good developer-friendly experience while ensuring the aforementioned criteria are not in peril. Designing for Resilience While designing APIs and SDKs, it is essential to place error handling at its core. To provide a dependable developer experience, a platform needs to have a comprehensive and well-documented error code system that covers a significant range of possible failure scenarios with dozens of unique error codes designed to cover various categories of errors like authentication failures, validation errors, resource not found, rate limiting, and other server-side errors. Furthermore, error messages should not only inform the developer about the nature of an error but offer guidance on how to resolve it. Offer retry mechanisms to developers when dealing with partial failures. Provide them with the means to configure the retry behavior, such as the maximum number of retries and initial retry delay. Additionally, set timeout values to prevent requests to services from hanging or being blocked indefinitely. Allows developers to customize the timeout setting and provides them with a way to gracefully cancel a long-running request. Follow an all-or-nothing approach when it comes to transactional operations. Keep data integrity and consistency in the forefront whenever a batch operation is invoked, either all operations in the batch should succeed or none of them should. The developer should be notified about which items in the batch were successful, and which items were erroneous. Ensure that your APIs and SDKs include robust logging capabilities that can help developers troubleshoot and debug issues. Log relevant information such as request/response details, error messages, and stack traces. Allow developers to configure logging verbosity and opt in/out of logging entirely in production. Define a consistent and clear versioning policy for your APIs and SDKs. Follow semantic versioning. Fostering a Developer Community Building a strong developer community around your APIs and SDKs is critical to drive adoption, educate developers, and promote innovation. Provide comprehensive documentation for your APIs and SDKs that thoroughly covers all they have to offer. Include getting started guides, tutorials, code samples, reference documentation, and more. Build an interactive developer portal that serves as the central hub for all developer-related content. Include features such as API consoles, sandbox environments, and interactive documentation that allow developers to experiment and try out their integrations in a controlled setting. Engage with developers through popular developer platforms, social media, webinars, and in-person workshops. Participate in discussions, answer questions, and provide support for developers who are using your APIs and SDKs. Create an environment where developers can easily provide feedback and help test and improve your offerings. Set up bug trackers, feature requests, and general feedback submission processes. Foster community-driven support by encouraging developers to help each other in forums, establish a community-driven knowledge base, and provide moderation to ensure a positive and inclusive community. Make sure your support team is responsive and knowledgeable, reply to developer questions promptly, and provide value-added responses. Keep a detailed internal knowledge base or a dedicated FAQ section containing solutions to common questions and challenges. This ensures your support and field teams can quickly understand and resolve customer issues, delivering a seamless experience to the developers using your APIs and SDKs. Organize developer events and conferences to gather developers and encourage one-on-one communication. Invite veterans and industry experts to educate and enlighten, and enable developers to present their own projects to learn from one another. Gather feedback, announce features or changes, and bond with your developer community. Growing a thriving developer community ensures you have a supportive environment that cultivates collaboration, education, and innovation, driving your APIs and SDKs to become more popular and successful. Iterate and Improve Develop a structured approach for assessing and ordering the feedback by its effect, urgency, and relationship with your company’s objectives. Regularly consult your development team and stakeholders to review the feedback and determine what changes and features should be implemented into your roadmap. Devote resources and set deadlines to implement the modifications. Ensure your development cycle includes complete testing and quality assurance procedures to uphold the integrity and dependability of your APIs and SDKs. Update your documentation and announce the changes to your developer community. Establish key performance indicators – API adoption rates, developer satisfaction, support ticket response time, for example – to evaluate your changes’ performance. Regularly monitor and assess this data to evaluate the effect of your changes and identify potential improvements. Lastly, build a culture of continuous learning and improvement within your organization. Ensure that your team keeps up with the latest trends in the industry, attends conferences and workshops, and participates in developer communities. Knowledge of the current trends equips you with relevant insights to stay ahead by addressing the current developers’ needs. More importantly, have processes that enable you to iterate and enhance the APIs and SDKs provided. Having a process that can effectively iterate shows developers that you are serious about delivering quality products and can quickly switch to another provider should their expectations be compromised. This way, you build trust and relationships that last, and your platform becomes a reliable and innovative tool that keeps attracting developers in the market. In conclusion, designing developer-friendly APIs and SDKs is a vital element of platform strategy. Prioritize simplicity, resilience, community, and continuous improvement. Remember, developers will only love your platform if they first enjoy using it. Hence, invest in making their experience better, meet their nowadays-changing needs, and enhance their satisfaction. Such actions enable you to get the best out of your platform, introduce more innovations, and thrive in the dynamic technology landscape.
I would like to to introduce you a Java class with less than 170 lines of code to facilitate work with SQL queries called via the JDBC API. What makes this solution interesting? The class can be embedded in a Java version 17 script. Using a Java Script The advantage of a Java script is easy portability in text format and the possibility of running without prior compilation, while we have considerable resources available from the language's standard library at runtime. The use of scripts is offered for various prototypes, in which even more complicated data exports or data conversions can be solved (after connecting to the database). Scripts are useful wherever we don't want to (or can't) put the implementation into a standard Java project. However, the use of the script has some limitations. For example, the code must be written in a single file. We can include all the necessary libraries when we run the script, but these will likely have additional dependencies, and simply listing them on the command line can be frustrating. The complications associated with the distribution of such a script probably do not need to be emphasized. For the above reasons, I believe that external libraries in scripts are best avoided. If we still want to go the script route, the choice falls on pure JDBC. Multi-line text literals can be advantageously used for writing SQL queries, and the automatic closing of objects like PreparedStatement (implementing the interface AutoCloseable). So what's the problem? Mapping SQL Parameter Values For security reasons, it is advisable to map SQL parameter values to question marks. I consider the main handicap of JDBC to be the mapping of parameters using the sequence number of the question mark (starting with one). The first version of the parameter mapping to the SQL script often turns out well, but the risk of error increases as the number of parameters and additional SQL modifications increase. I remind you that by inserting a new parameter in the first position, the following row must be renumbered. Another complication is the use of the operator IN because for each value of the enumeration, a question mark must be written in the SQL template which must be mapped to a separate parameter. If the parameter list is dynamic, the list of question marks in the SQL template must also be dynamic. Debugging a larger number of more complex SQLs can start to take a significant amount of time. For inserting SQL parameters using String Templates we will have to wait a little longer. However, inserting SQL parameters could be facilitated by a simple wrapper over the interfacePreparedStatement, which would (before calling the SQL statement) append the parameters using JPA-style named tags (alphanumeric text starting with a colon). A wrapper could also simplify reading data from the database (with a SELECT statement) if it allowed the necessary methods to be chained into a single statement, preferably with a return type Stream<ResultSet>. SqlParamBuilder Class Visualization of the SQL command with attached parameters would sometimes be useful for debugging or logging the SQL query. I present to you the class SqlParamBuilder. The priority of the implementation was to cover the stated requirements with a single Java class with minimalistic code. The programming interface was inspired by the library JDBI. The samples use the H2 database in in-memory mode. However, connecting the database driver will be necessary. Java void mainStart(Connection dbConnection) throws Exception { try (var builder = new SqlParamBuilder(dbConnection)) { System.out.println("# CREATE TABLE"); builder.sql(""" CREATE TABLE employee ( id INTEGER PRIMARY KEY , name VARCHAR(256) DEFAULT 'test' , code VARCHAR(1) , created DATE NOT NULL ) """) .execute(); System.out.println("# SINGLE INSERT"); builder.sql(""" INSERT INTO employee ( id, code, created ) VALUES ( :id, :code, :created ) """) .bind("id", 1) .bind("code", "T") .bind("created", someDate) .execute(); System.out.println("# MULTI INSERT"); builder.sql(""" INSERT INTO employee (id,code,created) VALUES (:id1,:code,:created), (:id2,:code,:created) """) .bind("id1", 2) .bind("id2", 3) .bind("code", "T") .bind("created", someDate.plusDays(7)) .execute(); builder.bind("id1", 11) .bind("id2", 12) .bind("code", "V") .execute(); System.out.println("# SELECT"); List<Employee> employees = builder.sql(""" SELECT t.id, t.name, t.created FROM employee t WHERE t.id < :id AND t.code IN (:code) ORDER BY t.id """) .bind("id", 10) .bind("code", "T", "V") .streamMap(rs -> new Employee( rs.getInt("id"), rs.getString("name"), rs.getObject("created", LocalDate.class))) .toList(); System.out.printf("# PRINT RESULT OF: %s%n", builder.toStringLine()); employees.stream() .forEach((Employee employee) -> System.out.println(employee)); assertEquals(3, employees.size()); assertEquals(1, employees.get(0).id); assertEquals("test", employees.get(0).name); assertEquals(someDate, employees.get(0).created); } } record Employee (int id, String name, LocalDate created) {} static class SqlParamBuilder {…} Usage Notes and Final Thoughts An instance of the type SqlParamBuilder can be recycled for multiple SQL statements. After calling the command, the parameters can be changed and the command can be run again. The parameters are assigned to the last used object PreparedStatement. Method sql() automatically closes the internal object PrepradedStatement (if there was one open before). If we change the group of parameters (typically for the IN operator), we need to send the same number for the same PreparedStatement. Otherwise, the method againsql() will need to be used. An object is required after the last command execution to explicitly close the SqlParamBuilder. However, since we are implementing an interface AutoCloseable, just enclose the entire block in a try block. Closing does not affect the contained database connection. In the Bash shell, the sample can be run with a script SqlExecutor.sh, which can download the necessary JDBC driver (here, for the H2 database). If we prefer Kotlin, we can try a Bash script SqlExecutorKt.sh, which migrates the prepared Kotlin code to a script and runs it. Let's not get confused by the fact that the class is stored in a Maven-type project. One reason is the ease of running JUnit tests. The class is licensed under the Apache License, Version 2.0. Probably the fastest way to create your own implementation is to download the example script, redesign the method mainRun(), and modify the connection parameters to your own database. Use your own JDBC driver to run.
1. Use "&&" to Link Two or More Commands Use “&&” to link two or more commands when you want the previous command to be succeeded before the next command. If you use “;” then it would still run the next command after “;” even if the command before “;” failed. So you would have to wait and run each command one by one. However, using "&&" ensures that the next command will only run if the preceding command finishes successfully. This allows you to add commands without waiting, move on to the next task, and check later. If the last command ran, it indicates that all previous commands ran successfully. Example: Shell ls /path/to/file.txt && cp /path/to/file.txt /backup/ The above example ensures that the previous command runs successfully and that the file "file.txt" exists. If the file doesn't exist, the second command after "&&" won't run and won't attempt to copy it. 2. Use “grep” With -A and -B Options One common use of the "grep" command is to identify specific errors from log files. However, using it with the -A and -B options provides additional context within a single command, and it displays lines after and before the searched text, which enhances visibility into related content. Example: Shell % grep -A 2 "java.io.IOException" logfile.txt java.io.IOException: Permission denied (open /path/to/file.txt) at java.io.FileOutputStream.<init>(FileOutputStream.java:53) at com.pkg.TestClass.writeFile(TestClass.java:258) Using grep with -A here will also show 2 lines after the “java.io.IOException” was found from the logfile.txt. Similarly, Shell grep "Ramesh" -B 3 rank-file.txt Name: John Wright, Rank: 23 Name: David Ross, Rank: 45 Name: Peter Taylor, Rank: 68 Name Ramesh Kumar, Rank: 36 Here, grep with -B option will also show 3 lines before the “Ramesh” was found from the rank-file.txt 3. Use “>” to Create an Empty File Just write > and then the filename to create an empty file with the name provided after > Example: Shell >my-file.txt It will create an empty file with "my-file.txt" name in the current directory. 4. Use “rsync” for Backups "rsync" is a useful command for regular backups as it saves time by transferring only the differences between the source and destination. This feature is especially beneficial when creating backups over a network. Example: Shell rsync -avz /path/to/source_directory/ user@remotehost:/path/to/destination_directory/ 5. Use Tab Completion Using tab completion as a habit is faster than manually selecting filenames and pressing Enter. Typing the initial letters of filenames and utilizing Tab completion streamlines the process and is more efficient. 6. Use “man” Pages Instead of reaching the web to find the usage of a command, a quick way would be to use the “man” command to find out the manual of that command. This approach not only saves time but also ensures accuracy, as command options can vary based on the installed version. By accessing the manual directly, you get precise details tailored to your existing version. Example: Shell man ps It will get the manual page for the “ps” command 7. Create Scripts For repetitive tasks, create small shell scripts that chain commands and perform actions based on conditions. This saves time and reduces risks in complex operations. Conclusion In conclusion, becoming familiar with these Linux commands and tips can significantly boost productivity and streamline workflow on the command line. By using techniques like command chaining, context-aware searching, efficient file management, and automation through scripts, users can save time, reduce errors, and optimize their Linux experience.
Turbocharge Innovation With Automated API Generation
April 28, 2024 by CORE
Get Some Rest! A Full API Stack
April 27, 2024 by CORE
Explainable AI: Making the Black Box Transparent
May 16, 2023 by CORE
Turbocharge Innovation With Automated API Generation
April 28, 2024 by CORE
Get Some Rest! A Full API Stack
April 27, 2024 by CORE
Low Code vs. Traditional Development: A Comprehensive Comparison
May 16, 2023 by
April 26, 2024 by
Solving PostgreSQL Indexes, Partitioning, and LockManager Limitations
April 26, 2024 by
Get Some Rest! A Full API Stack
April 27, 2024 by CORE
If Software Quality Is Everybody’s Responsibility, So Is Failure
April 26, 2024 by
Low Code vs. Traditional Development: A Comprehensive Comparison
May 16, 2023 by
Diffusion and Denoising: Explaining Text-To-Image Generative AI
April 26, 2024 by
Revolutionizing Business Intelligence: The Role of Explainable AI and Empowering Non-Technical Users
April 26, 2024 by CORE
Five IntelliJ Idea Plugins That Will Change the Way You Code
May 15, 2023 by