The 10 biggest software companies with over 700 employees united in the Bit Alliance presented their former success, but also the potential of this sector. These companies consider that we have the potential to educate 25.000 software developers and that IT should become the most strong industry in the country.
First conference on the development of B&H’s software industry
Amela Trokic
Ready to Achieve More?
We’ll help you reach your goals quickly with an easy and straightforward process to kick off our collaboration. Here’s what happens next.
STEP 1
Discovery Call
Let’s chat to understand your company, project needs, and answer any questions along the way.
STEP 2
Free Consultation
Work closely with our experts to explore the right solutions for your business.
STEP 3
Collaboration Proposal
We'll recommend the best strategy for your goals, ensuring you get the most from our expertise.
STEP 4
30-Day Cancellation
Policy Contract
Spoiler: It’s Never Been Used
Enjoy peace of mind while we deliver excellence from day one—our track record speaks for itself.
Thank you for reaching out to us!
We’ll get back to you soon. This window will close automatically in 5 seconds.
Read more about similar topics
July 29, 2026
Software Development
Evaluating LLMs Using DeepEval
Introduction At the time of writing this blog, it seems the most popular topics in software engineering are Artificial Intelligence (AI) and Large Language Models (LLMs). LLMs are being proactively integrated into production systems for various purposes and goals, leading to the critical question: “How can we be certain the model performs well?” Traditional software is deterministic and can be tested with clear pass/fail decisions. On the other hand, LLMs are notorious for being non-deterministic, which is embedded in their very nature. Without getting into details on the underlying architecture or how they perceive the world, LLMs are known for their hallucinations, drifting away from their targeted task, being too agreeable, etc. These types of problems force us to adopt a different approach when it comes to grading the quality of our LLM-dependent systems. We shift our perspective to evaluation, i.e., scoring the output by a predefined scale, usually [0,1], instead of the traditional binary pass/fail. Different frameworks try to provide the best solution, and the one that stands out amongst many is DeepEval. What is DeepEval? DeepEval is an open-source Python evaluation framework for LLMs. With the main goal being to unit test LLMs, DeepEval operates similarly to Pytest, along with the additional caveats attributed to LLMs. DeepEval supports end-to-end as well as component-level evaluation. DeepEval’s approach to evaluation relies on the following components: Test Case - a single interaction between the user and the LLM. This is a container that includes data for a specific evaluation and consists of: input - the prompt sent to the LLM actual_output - the recorded LLM response expected_output - the gold standard response for comparison (if available) Metrics - definition of the scoring rule used to evaluate the Test Case. They may be: Deterministic - use fixed algorithms and formulae (ex., exact matches, regex, ROUGE/BLEU, LaBSE...) Non-deterministic (LLM-as-a-Judge) - use an independent powerful model to “read” and assess the output. Evaluation Dataset - collection of Test Cases grouped together Individual Test Case Evaluation Ideal evaluation workflow using DeepEval Basic evaluation through an example The problem We can imagine working on an LLM-based assistant that answers questions from retrieved documents. How do we verify whether the response is faithful to the source and is semantically correct? Setup pip install deepeval sentence-transformersexport OPENAI_API_KEY="your-api-key-here" The OpenAI key is needed for the LLM-as-a-judge metric, while deterministic metrics run without external calls. The test case from deepeval.test_case import LLMTestCasetest_case = LLMTestCase( input="What is Varnish?", actual_output="Varnish is an HTTP reverse proxy that caches responses in RAM.", expected_output="Varnish is a caching HTTP reverse proxy that accelerates web applications.", retrieval_context=["Varnish is a HTTP reverse proxy and a caching server that ","speeds up web applications by caching HTTP responses." ]) Two metrics will be used for this test case. One is an LLM-as-a-Judge metric, and the other is a deterministic metric that will utilize LaBSE. The sentence-transformers package is used for LaBSE. The retrieval_context is text fetched from a knowledge base that the LLM uses as source material when generating its answer. The retrieval_context is needed when we want to test the model against specific issues. (hallucinations, etc.) Language-agnostic BERT Sentence Embedding (LaBSE) is a multilingual sentence embedding model. It is used to compute the cosine similarity between the actual and expected outputs to evaluate semantic similarity. Metric 1: Faithfulness (LLM-as-a-Judge) LLM-as-a-Judge metrics use a secondary independent LLM to evaluate and score the primary model’s output. DeepEval has different built-in options: from deepeval.metrics import ( AnswerRelevancyMetric, # Does the output answer the question? FaithfulnessMetric, # Is the output grounded in the retrieval context? ContextualRelevancyMetric,# Is the retrieved context relevant to the question? HallucinationMetric, # Does the output contain fabricated claims not in the context? GEval, # Custom LLM-judged metric using criteria you define. The criteria is defined via a separate prompt.) For our use case, we will use the Faithfulness metric to check if the actual LLM output is grounded in the retrieval context. from deepeval.metrics import FaithfulnessMetricfaithfulness = FaithfulnessMetric(threshold=0.8) Because the evaluation itself is performed by an LLM, scores may slightly vary between runs. Metric 2: LaBSE semantic similarity (deterministic) from deepeval.metrics import BaseMetricfrom deepeval.test_case import LLMTestCasefrom sentence_transformers import SentenceTransformer, utilclass LaBSESimilarityMetric(BaseMetric): """Deterministic semantic similarity using LaBSE embeddings.""" def __init__(self, threshold: float = 0.7): self.threshold = threshold self.score = 0 self.success = False self.model = SentenceTransformer("sentence-transformers/LaBSE") async def a_measure(self, test_case: LLMTestCase, *args, **kwargs) -> float: return self.measure(test_case) def measure(self, test_case: LLMTestCase) -> float: embeddings = self.model.encode( [test_case.actual_output, test_case.expected_output], convert_to_tensor=True, ) self.score = util.cos_sim(embeddings[0], embeddings[1]).item() self.success = self.score >= self.threshold return self.score def is_successful(self) -> bool: return self.success @property def __name__(self): return "LaBSE Semantic Similarity" The class extends BaseMetric from DeepEval and implements: measure — encodes both actual_output and expected_output into embeddings, computes their cosine similarity, and tests it against the threshold. a_measure — async wrapper required by DeepEval's interface is_successful — returns whether the measurement passed the threshold. For the same inputs, LaBSE always produces the same embeddings, which follow with the same cosine similarity score. As a result, this metric is fully deterministic and reproducible. Running both metrics on one test case from deepeval import assert_testdef test_varnish_response(): assert_test(test_case, [ FaithfulnessMetric(threshold=0.8), # non-deterministic LaBSESimilarityMetric(threshold=0.7), # deterministic ]) Assuming the file containing the mentioned code is named test_varnish.py, the tests can be invoked through the following command: deepeval test run test_varnish.py Running the command produces the output: The test passes only if both metrics meet their thresholds. The faithfulness check ensures the output doesn't hallucinate beyond the context, while the LaBSE similarity check ensures it remains semantically close to the expected answer. Conclusion LLM-as-a-Judge can be used for free-form text where it is hard (or sometimes impossible) to define the correctness of an answer. (summaries, explanations, faithfulness, etc.) Deterministic metrics should be used for tasks with clearly defined correct outputs and where reproducible scores are needed. Another advantage of deterministic metrics is the avoidance of LLM-judge-related costs. In practice, the most effective evaluation suites combine both approaches. DeepEval provides a bridge between the familiar world of Pytest and the modern requirements of LLM testing. By combining LLM-judged metrics for subjective quality assessment with deterministic metrics for measurable correctness, it provides a complete evaluation toolkit that can be easily run from the CLI or a CI pipeline. References: Feng, F., Yang, Y., Cer, D., Arivazhagan, N., & Wang, W. (2020, July 3). Language-agnostic BERT sentence embedding. arXiv.org. https://arxiv.org/abs/2007.01852 DeepEval by Confident AI. (2026, April 5). Quick Introduction. https://deepeval.com/docs/getting-started LLM-as-a-Judge Metrics | Confident AI Docs. (n.d.). Confident AI Docs. https://www.confident-ai.com/docs/llm-evaluation/core-concepts/llm-as-a-judge Ip, J. (2025, October 10). LLM-as-a-Judge simply explained: The complete guide to run LLM evals at scale. Confident AI. https://www.confident-ai.com/blog/why-llm-as-a-judge-is-the-best-llm-evaluation-method DeepEval by Confident AI. (2026a, April 5). “Do it yourself” Metrics. https://deepeval.com/docs/metrics-custom Zheng, L., Chiang, W., Sheng, Y., Zhuang, S., Wu, Z., Zhuang, Y., Lin, Z., Li, Z., Li, D., Xing, E. P., Zhang, H., Gonzalez, J. E., & Stoica, I. (2023, June 9). Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena. arXiv.org. https://arxiv.org/abs/2306.05685
April 29, 2021
Software Development
A couple of months ago I had my first encounter with InfluxDB. I found it very interesting from the very start because its key concepts are different from the ones used in the SQL or MongoDB databases, in which I had some experience. The main obstacle for any programmer is, in my opinion, a lack of helpful resources available online. This is the very problem with InfluxDB. So, here I will attempt to make it somewhat easier to deploy and understand how the deployment of InfluxDB is done on Kubernetes. This blog will be divided into three parts: Introduction to InfluxDB Deployment and resources Wrap up with a few words So, let’s get started! About InfluxDB To be able to properly define InfluxDB let’s first define the data it usually stores. Time series data is a sequence of data points, typically consisting of successive measurements, over some time interval. So, InfluxDB is an open-source database optimized for the time series data stage. With this said, it is easy to assume that every piece of data inside InfluxDB has an exact time when it is measured, or at least a time when it is written into the database. InfluxDB is made to work with a high load of point writes and point reads. This makes it a very good choice if we want to set up some kind of monitoring where time precision is of great importance. Key concepts and data elements There are certain elements that all data inside of InfluxDB consists of. Below is a simple description of all of them. Timestamp - the time at which our measure is written into InfluxDB Fieldset - a set of key-value pairs (field_name and field_value). At least one field is necessary so the data we wish to save into InfluxDB is valid. A valid type for a field value is a string, float, integer, and boolean. Tag set - a set of key-value pairs (tag_name and tag_value). Unlike fields, tags are indexed. That means querying with tags is faster than querying with fields. So, tags contain commonly queried data. Tag values can only be strings. Measurements - are the place where we store the elements above. The measurement name should describe the data which is stored in it. Series - a collection of points that share a measurement, tag set, and field key. Buckets - buckets are containers for all the elements above. Each bucket has a retention policy that serves as lifecycle management. Basically, it defines the lifespan of the data inside the bucket. Organization - consists of buckets and their users. The next diagram shows how some of these elements are related. Simple GO-InfluxDB application When learning new things, I always find it easier if I have something I can run. So, I prepared a simple application that would have helped me when I was starting with InfluxDB. All the code can be found here huseincausevic-abh along with instructions on how to run it. The application goal was to deploy a simple API written in Go programming language along with InfluxDB and to ensure that communication with InfluxDB is established. Before we begin, to follow these examples and to be able to deploy the application on Kubernetes you should have a Kubernetes cluster and Kubectl command-line tool to communicate with the cluster. Now, with that said, we can deploy our application. Deploying InfluxDB to Kubernetes To successfully deploy InfluxDB we have to write a couple of resources. Since, InfluxDB is a database, at any time it has some kind of state which must be persisted. For this purpose, we are using the StatefulSet Kubernetes resource. It grants unique network identifiers and stable persistent storage for all the pods defined in the manifest file, which is useful if we want to scale our application later on. Our high-level goal is to: Have one InfluxDB instance running; Ensure that traffic is possible as soon as the InfluxDB pod is up; Perform periodical health checks to see if everything is running as desired/expected. The manifest file that describes the bullets above is: apiVersion: apps/v1 kind: StatefulSet metadata: labels: app: influxdb-demo name: influxdb-demo spec: replicas: 1 selector: matchLabels: app: influxdb-demo serviceName: influxdb-demo template: metadata: labels: app: influxdb-demo spec: containers: - image: quay.io/influxdb/influxdb:2.0.0-beta imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /health port: api scheme: HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 name: influxdb-demo ports: - containerPort: 9999 name: api protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /health port: api scheme: HTTP initialDelaySeconds: 5 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 Here we said that we want to build our InfluxDB container from the quay.io/influxdb/influxdb:2.0.0-beta image that is located on Red Hat’s images repository. The readiness and Liveness probe ensures the second and third bullets respectively. Both probes are functioning in a similar way. The liveness probe is performed periodically after the pod is marked as running, causing the pod to restart if something is not as expected. The readiness probe, on the other hand, will not restart the pod but it will remove the endpoint from the InfluxDB Kubernetes service which points to that specific pod. InfluxDB has a health check defined on the path /health that will tell us whether it is running correctly or not, so our probe success statuses are based on its return value. If this probe fails we would get something like this after describing the influxdb-demo pod: $ kubectl describe pod -n husein influxdb-demo-0 Name: influxdb-demo-0 Namespace: husein Priority: 0 . . . Warning Unhealthy 58s kubelet, .ec2.internal Readiness probe failed: Get http://10.0.10.57:9999/health: dial tcp 10.0.10.57:9999: connect: connection Now if we grab the stateful set manifest file and apply it to the cluster, we should be able to see that it is running: husein:~/r8/r8/go-Influxdb-simple-app/k8s$ kubectl apply -f influxdb-statefulset.yaml -n husein statefulset.apps/influxdb-demo created husein:~/r8/r8/go-Influxdb-simple-app/k8s$ kubectl get pods -n husein | grep influxdb-demo influxdb-demo-0 1/1 Running 0 26s The next thing we need to define is the InfluxDB service, so we can communicate with the created pod. --- apiVersion: v1 kind: Service metadata: labels: app: influxdb-demo name: influxdb-demo spec: type: NodePort ports: - name: api port: 9999 targetPort: 9999 nodePort: 31234 selector: app: influxdb-demo This service is going to target port 9999 of all the pods that have the label app: influxdb-demo defined, which our pod in the previously created resource has. In the example above we also used the NodePort service type. This is usually only used for dev purposes, but for this example, it is good to help demonstrate what we achieved so far. Let’s apply it to the cluster so we can see. husein:~/r8/r8/go-Influxdb-simple-app/k8s$ kubectl apply -f influxdb-service.yaml -n husein service/influxdb-demo created husein:~/r8/r8/go-Influxdb-simple-app/k8s$ kubectl get svc -n husein | grep influxdb-demo influxdb-demo NodePort 172.20.223.47 9999:31234/TCP 14s After we defined the service, we can get the ExternalDNS so we can access our InfluxDB through the web browser. # get nodes name $ kubectl get nodes | awk ‘{ print $1 }’ # copy one and do $ kubectl describe node | grep ExternalDNS After that, you should be able to see the Chronograf dashboard for InfluxDB data. Chronograf is the user interface and administrative component of InfluxDB. We can access it on URL: ExternalDNS:3124. You will be able to see this: Great, we successfully deployed InfluxDB. Before we can continue with setting up our InfluxDB instance, let’s go over Go application resources. GO application resources The service resource for this application is almost identical to the one we wrote for InfluxDB. So, I won’t go over it here. As I said, this Go application is a simple API that only processes data it gets, and then sends the processed request to InfluxDB. This means this is a stateless application. For this purpose, we will use the Deployment resource instead of the StatefulSet that we used for InfluxDB. Here is the resource definition: apiVersion: apps/v1 kind: Deployment metadata: name: go-app labels: app: go-app spec: replicas: 1 selector: matchLabels: app: go-app template: metadata: labels: app: go-app spec: containers: - image: hcausevic5/go-influxdb-simple-app name: go-app imagePullPolicy: Always ports: - containerPort: 4444 volumeMounts: - name: influx-creds mountPath: /app/influxdb readOnly: true volumes: - name: influx-creds secret: secretName: influxdb-auth-demo It should be pretty clear what we want to achieve here. We will have one pod (replicas: 1) and that pod will have one container built from go-influxdb-simple-app image on my DockerHub account. We will put it under the app-service with the label: app: go-app and it should receive our requests on port 4444. There are also volume mounts that I find interesting. Why would we need that? Further InfluxDB setup Basically, before we can do anything with our InfluxDB instance we need to do a setup. The setup will define the initial user, bucket and organization. Along with a bunch of other stuff, in return, we will get an authentication token which we need to do writes, queries, etc. In our Go code, we need that token so we can communicate with the InfluxDB instance. Of course, it is possible to get into the InfluxDB container or do a manual setup with Chronograf’s dashboard UI but then we will also need to manually change it in Go code. For that purpose, we will define another resource: Secret. It looks something like this: --- apiVersion: v1 kind: Secret type: Opaque metadata: name: influxdb-auth-demo data: url: aHR0cDovL2luZmx1eGRiLWRlbW86OTk5OQ== username: aHVzZWlu password: aHVzZWluMTIz org: bXktb3Jn bucket: bXktYnVja2V0 This secret contains all the necessary data we need to successfully set up InfluxDB. But what are those hieroglyphs, you might ask. Well, Kubernetes’ secrets require the data value field to be base64 encoded. Encoding and decoding can be performed like this: $ echo -n http://influxdb-demo:9999 | base64 -w 0 aHR0cDovL2luZmx1eGRiLWRlbW86OTk5OQ== $ echo -n aHR0cDovL2luZmx1eGRiLWRlbW86OTk5OQ== | base64 —decode http://influxdb-demo:9999 We introduced this resource just for the token, and it hasn’t even shown up? Well, not yet. As I already said the token is just a piece of data that we get after setting up InfluxDB, which we need for communication through the Go client library for InfluxDB. We don’t know what this value will be, and we don’t need to know if we automate the process of setting up InfluxDB and saving the token. For automation purposes, we will be building another resource: Job. If we look up InfluxDB API docs, we can see that API Endpoint (/api/v2/setup) can be used for this. The job of the Job (:D) is to set up our InfluxDB instance using the values from the previously created secret and then patch the secret with the token value it gets in return. This secret will be mounted into the /app/influxdb directory of the pod. The next code shows us how we can extract that data in our GO code: func mountedConnectionParameters() map[string]string { connectionParams := make(map[string]string) basePath := "/app/influxdb" files, err := ioutil.ReadDir(basePath) if err != nil { panic(err) } for _, file := range files { if strings.HasPrefix(file.Name(), ".") == false { fileContent, err := ioutil.ReadFile(fmt.Sprintf("%v/%v", basePath, file.Name())) if err != nil { logrus.Errorf("Could not read file %v", file.Name()) } connectionParams[file.Name()] = string(fileContent) } } return connectionParams } The function above will return all the connection parameters we used to set up our InfluxDB, and with that data, we can successfully send our queries to InfluxDB using the influxdb-client-go package. Also, since we already made some effort to automate the InfluxDB setup, we can automate the whole setup. This will be made with bash script: deploy.sh. This script can be run with two parameters: (-n) namespace, (-m, apply delete recreate) mode. $ bash deploy.sh -n husein -m recreate husein:~/r8/r8/go-Influxdb-simple-app/k8s$ bash deploy.sh -n husein -m apply ------------------------------------------------------- Using mode: apply on resources... ------------------------------------------------------- ------------------------------------------------------- Applying InfluxDB resources... ------------------------------------------------------- secret/influxdb-auth-demo created service/influxdb-demo created statefulset.apps/influxdb-demo created Waiting for InfluxDB pod to be ready... Waiting for InfluxDB pod to be ready... Waiting for InfluxDB pod to be ready... Waiting for InfluxDB pod to be ready... Waiting for InfluxDB pod to be ready... InfluxDB pod is ready! job.batch/influxdb-set-authentication created service/go-app created deployment.apps/go-app created ——————————————————————————— Let's see If everything runs as expected. You can use Postman to send a request to the ExternalDNS:APP_PORT. As we can see our simple go application for writing and reading temperatures into InfluxDB is working as expected. Wrapping up - Is that it? As for the basic InfluxDB setup, yes that’s it. Of course, we could do a lot more to upgrade our little example, but this should get you going. Our example covered only a little segment of the things both of these technologies have to offer. Now, there is a common question of whether or not you should use InfluxDB. Well, it depends. There are some fields in which InfluxDB might be an excellent idea, and somewhere it wouldn’t. It all depends on the data we want to store. If we have some time-sensitive data, if we want to do some kind of monitoring, then sure it might be a really good choice. Like any other database, InfluxDB has a field of applications that suit it best. There is no rule that tells us that we cannot use InfluxDB with data that isn’t time-sensitive, but we should choose the right database for the data that we are storing not the other way around.
January 29, 2021
News
Participate in Atlantbh’s Virtual DevDay 2021
What is Atlantbh's Virtual DevDay? The DevDays workshop is a web app development workshop that gives participants the chance to see what it is like to work in a real project team on an actual project with client needs and inputs. Project teams will consist of four positions: Software Engineer, Test Engineer, UI/UX Designer, and Scrum Master/Product Owner. While there is a lot of information about the different roles available in the IT industry, very rarely do young people get the chance to test out these positions before they commit to them. The DevDays workshop is sort of like job shadowing, where you get to try out a position of your choice with no career commitment because guess what, this is just trial-and-error for your future career choice. Basically it’s time to Fake it ‘till you make it! In previous years, we've held our DevDay workshops throughout the country, from Bihać to Sarajevo, and last year we held the first-ever Virtual DevDay. Since we got a great feedback after our virtual event, we decided to do it again this year as well. Education has no limitation and we are committed to providing opportunities to enthusiastic future IT superstars. What positions can I apply for? Anyone can apply with full or partial teams, or as individuals. For those who don’t have a full team, Atlantbh will match them with other participants in the same situation. The positions include: Software Engineer – You will be required to build a UI interface by creating components and templates, based on a provided design mock-up, coupling them into a functional frontend application. You will be expected to implement new features and fix bugs on the existing application codebase. You will also work with the rest of the team to ensure it’s done correctly, according to specifications and possibly design. You will be working with technologies such as Ember.js, Java Play, HTML, and CSS. Requirements: You are familiar with the basics of OOP, proficient in at least one programming language, and familiar with code versioning tools, such as Git. QA/Test Engineer – You will be expected to quickly adopt domain knowledge of the application, understanding how it functions, and the purpose of its features. You will improve and extend the provided test suites as well as write functional test scripts for the application. You will also make use of both manual and automation testing to detect and eradicate bugs. Requirements: You are a detail-oriented person who can communicate well, ready to challenge or question the developer when needed. UX/UI Designer – You will be required to gather and evaluate user requirements, in collaboration with the product owner and engineers. You will illustrate design ideas using storyboards, process flows, and sitemaps. You will design graphic user interface elements, like menus, tabs, and widgets. Requirements: You are a creative person with out-of-the-box thinking and a strong understanding of design rules and user functionality. Product Owner – You will be expected to organize and manage your team’s tasks using Agile Project Management principles. You will assign tickets and ensure that they are completed according to schedule. You will be the main channel of communication between your product team and the client, ensuring that your team produces what the client wants, all while optimizing processes. Requirements: You are highly organized, have strong communication skills, and understand the different roles in a project team. When is Atlantbh's Virtual DevDay? This year’s Virtual DevDays will be held on the 22nd and 23rd of February, online. Applications are currently open and future participants can apply here by February 10th, 2020. All detailed information about the workshop will be sent by email once you’ve completed the application form.