code: def _af_filter(data, in_file, out_file):
      
      min_freq = float(utils.get_in(data["config"], ("algorithm", "min_allele_fraction"), 10)) / 100.0
      logger.debug("Filtering MuTect2 calls with allele fraction threshold of %s" % min_freq)
      ungz_out_file = "%s.vcf" % utils.splitext_plus(out_file)[0]
      if not utils.file_exists(ungz_out_file) and not utils.file_exists(ungz_out_file + ".gz"):
          with file_transaction(data, ungz_out_file) as tx_out_file:
              vcf = cyvcf2.VCF(in_file)
              vcf.add_filter_to_header({
                  'ID': 'MinAF',
                  'Description': 'Allele frequency is lower than %s%% ' % (min_freq*100) + (
                      '(configured in bcbio as min_allele_fraction)'
                      if utils.get_in(data["config"], ("algorithm", "min_allele_fraction"))
                      else '(default threshold in bcbio; override with min_allele_fraction in the algorithm section)')})
              w = cyvcf2.Writer(tx_out_file, vcf)
              # GATK 3.x can produce VCFs without sample names for empty VCFs
              try:
                  tumor_index = vcf.samples.index(dd.get_sample_name(data))
              except ValueError:
                  tumor_index = None
              for rec in vcf:
                  if tumor_index is not None and np.all(rec.format('AF')[tumor_index] < min_freq):
                      vcfutils.cyvcf_add_filter(rec, 'MinAF')
                  w.write_record(rec)
              w.close()
      return vcfutils.bgzip_and_index(ungz_out_file, data["config"])
docs: Soft-filter variants with AF below min_allele_fraction (appends "MinAF" to FILTER)