先看效果

系统架构组件

  1. Streamlit 界面

    • 侧边栏和输入区域:提供用户界面,用于输入数据(如 Azure 端点)并进行配置。
    • 聊天输入和输出区域:主要区域,用户在此与聊天助手交互并查看结果。
  2. LLM(大型语言模型)配置设置

    • OpenAI 配置:配置用于使用 OpenAI 模型(如 gpt-4o-2024-08-06)。
    • 本地 LLM 配置:本地 LLM 模型(如 ollama/llama3:latest)的配置。
  3. 助手代理(AssistantAgent)和用户代理(UserProxyAgent)

    • TrackableAssistantAgent:继承自 AssistantAgent,负责与用户输入进行处理和响应,同时集成在 Streamlit 中以显示聊天消息。
    • TrackableUserProxyAgent:继承自 UserProxyAgent,用于接收用户输入,处理用户命令,并在助手代理与用户之间进行代理交互。
  4. 实用工具函数

    • get_url_info_from_kong:从 Kong API 网关中查询 URL 的路由、服务和上游配置的信息,并返回格式化结果。
    • dns_record_status:检查给定 URL 的 DNS 记录状态。
    • query_from_cmdb:从 CMDB(配置管理数据库)中检索特定云服务提供商(如阿里云、AWS 等)的服务器、数据库和中间件实例的数量。
  5. 异步聊天系统

    • 使用异步事件循环(asyncio),用户代理(User Proxy Agent)可以异步与助手代理(Assistant Agent)进行对话,提供更高效的交互体验。

架构图

+---------------------------------------------------------------+
|                      Streamlit Interface                      |
|---------------------------------------------------------------|
| +-----------------------------------------------------------+ |
| |  Sidebar (Azure Endpoint Config, etc.)                    | |
| +-----------------------------------------------------------+ |
|                                                               |
| +-----------------------------------------------------------+ |
| |                  Chat Input / Output Area                 | |
| |                                                           | |
| |  User Input --> UserProxyAgent --> AssistantAgent         | |
| |                                                           | |
| |  AssistantAgent --> UserProxyAgent --> Output Display     | |
| +-----------------------------------------------------------+ |
+---------------------------------------------------------------+

+--------------------+               +--------------------+
| LLM Configurations |               |   Utility Functions|
|--------------------|               |--------------------|
| - OpenAI (GPT-4)   |               | - get_url_info_from|
| - Local LLM (LLaMA)|               |   _kong()          |
+--------------------+               |   (Interacts with  |
                                     |    Kong API Gateway)|
                                     | - dns_record_status|
                                     |   (Checks DNS)     |
                                     | - query_from_cmdb  |
                                     |   (Interacts with  |
                                     |    CMDB Database)  |
                                     +--------------------+

   +-----------------+               +-------------------+
   | AssistantAgent  | <--- asyncio ->| UserProxyAgent    |
   | (Handles LLM    |               | (Manages User     |
   |  Requests)      |               |  Input/Commands)  |
   +-----------------+               +-------------------+
        ^    |                              ^    |
        |    |                              |    |
        |    v                              |    v
+----------------+                     +-------------------+
|   LLM Config   |                     |  Utility Functions|
|  Setup (OpenAI)|                     | (Kong, DNS, CMDB) |
+----------------+                     +-------------------+

                     +----------------+
                     |   Data Flow    |
                     |----------------|
                     |  - User Input  |
                     |  - Assistant   |
                     |  - ProxyAgent  |
                     |  - Utility Func|
                     +----------------+

架构图描述

  1. 用户输入(通过 Streamlit)
    用户通过 Streamlit 界面输入聊天内容或命令。
  2. 助手代理和用户代理交互
    用户代理接收用户输入,解析并处理命令,然后与助手代理交互。助手代理根据注册的工具函数或LLM配置进行响应。
  3. 工具函数交互
    当助手代理或用户代理调用工具函数时,这些函数将与 Kong API 网关、DNS 解析服务或 CMDB 模拟数据进行交互。
  4. 结果显示
    通过 Streamlit 界面将助手代理和用户代理的响应结果显示给用户。

核心优势

  1. 超越简单的 RAG 和提示词工程

    • 传统的 RAG 方法主要依赖于检索和生成的结合,通过从知识库中检索相关信息并用语言模型生成答案。然而,这种方法局限于信息查询和简单的问答系统,无法处理更复杂的任务。
    • 提示词工程则是通过精细设计提示词来引导语言模型生成特定输出,依然依赖于语言模型本身的生成能力,不能主动与外部系统进行交互或执行特定操作。
  2. 使用 Function Call 完成真实世界的任务

    • 本系统通过引入 Function Call 技术,赋予助手代理(Assistant Agent)和用户代理(User Proxy Agent)调用实际功能的能力。这些功能可以执行复杂的任务,如查询 Kong API 网关中的服务配置、检查 DNS 记录状态、从 CMDB 检索云资源信息等。
    • 通过注册和调用实际的 Python 函数,系统能够与外部 API、数据库和服务进行交互,执行逻辑操作和数据处理。这种能力使得系统不仅限于简单的对话和问答,更能够执行真实世界中的操作任务。
  3. 集成异步交互和高效任务处理

    • 使用异步框架(如 asyncio)实现用户代理和助手代理之间的异步通信,大幅提升了任务处理的效率和响应速度。这样的设计确保了系统能够并发处理多个任务,而不阻塞用户输入和系统响应。
    • 异步处理机制也增强了系统的稳定性和扩展性,使其能够处理更大规模的请求和更复杂的任务逻辑。
  4. 技术含量高,解决复杂场景问题

    • 系统架构充分考虑了实际应用场景中的复杂性,通过模块化设计,支持各种工具函数的集成和扩展,能够适应不同的企业和业务需求。
    • 例如,get_url_info_from_kong 函数能够通过调用 Kong API,获取详细的路由、服务和插件信息,并对这些数据进行格式化处理和展示;query_from_cmdb 函数能够从 CMDB 中动态检索并整合不同云服务商的资源信息。这样的功能大大提升了系统的实际应用价值。
  5. 提升企业运营效率与智能化水平

    • 通过整合各种实用功能和自动化操作,系统能够显著提升企业运维和运营效率。例如,它可以自动查询和管理 API 网关配置、检查网络 DNS 状态、整合和分析云资源数据,帮助企业做出更高效的决策和管理。

最近在整理CMDB信息,以Jenkins为中枢,统计、梳理代码仓库位置、发布位置。形成以应用为中心,串连资源、管理者。
首先需要统计代码中涉及的配置文件信息,比如Mysql/Redis/Elasticsearch/MongoDB/Kafka/RocketMQ/MQTT/Doris/HBase/InfLuxDB/http等
意义:

  • 排错时参考,比如该服务报504错误,马上定位到具体的中间件或者外部接口
  • 不遵守配置规范,不用配置中心硬编码在代码中的情况,能被发现
  • 资源收拢,当所有代码中都未出现的资源可作为下架依据

相较传统的正则匹配,大模型加持下有如下优点

  • 大模型能够识别到更多的配置,正则更依赖规则,对于语言多、规范复杂、执行不严有优势
  • 大模型能够用简单的提示词,格式化、筛选输出

环境介绍

  • 效果展示,使用了内部CMDB项目代码,使用了Python的Django框架,代码大小5M。项目中的配置相对分散。分析过程30秒。

效果.jpg

  • 代码、配置资产属于机密信息,故使用近期评分较高的本地模型确保安全性,Meta-Llama-3.1-8B-Instruct/Qwen2-7B-Instruct,使用vllm部署
#!/bin/bash
  
# 设置环境变量
export CUDA_VISIBLE_DEVICES=0,1

# 启动 vllm 服务器并将其转移到后台运行
nohup python3 -m vllm.entrypoints.openai.api_server \
    --model /data/vllm/Meta-Llama-3.1-8B-Instruct \
    --served-model-name llama \
    --tensor-parallel-size 2 \
    --trust-remote-code > llama.log 2>&1 &
  • 逻辑如下,通过多个模型分别判断,取并集后再利用模型整合

20240812163027.jpg

import os
import re
import requests
import time

# 定义中间件关键字的正则表达式,忽略大小写
KEYWORDS = ["mysql", "redis", "elasticsearch", "mongodb", "kafka", "rocketmq",
            "rabbitmq", "emq", "mqtt", "nacos", "postgresql", "doris",
            "hbase", "influxdb", "azkaban", "sls", "clickhouse",
            "mse", "dataworks", "neo4j", "http", "gitlab", "jenkins"]
PATTERN = re.compile(r'\b(?:' + '|'.join(KEYWORDS) + r')\b', re.IGNORECASE)


def read_files(directory):
    for root, _, files in os.walk(directory):
        # 忽略 .git 文件夹
        if '.git' in root:
            continue
        for file in files:
            file_path = os.path.join(root, file)
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.readlines()
                yield file_path, content


def extract_context(content, file_path):
    results = []
    for i, line in enumerate(content):
        if PATTERN.search(line):
            start = i  # 从匹配到的行开始
            end = min(i + 11, len(content))  # 包含匹配行及其下方10行
            snippet = "".join(content[start:end]).strip()
            results.append(f"文件路径: {file_path}\n{snippet}")
    return results


def write_to_file(directory, contexts):
    output_file = os.path.join(directory, 'matched_content.txt')
    with open(output_file, 'w', encoding='utf-8') as f:
        for context in contexts:
            f.write(context + '\n' + '=' * 50 + '\n')
    return output_file


def send_to_model(url, model_name, prompt, content):
    headers = {"Content-Type": "application/json"}
    data = {
        "model": model_name,
        "temperature": 0.2,
        "messages": [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": f"{prompt}\n\n{content}"}
        ]
    }

    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
    except requests.RequestException as e:
        print(f"Request to model failed: {e}")
        return None

    response_json = response.json()
    if 'choices' not in response_json:
        print(f"Model response does not contain 'choices': {response_json}")
        return None

    return response_json['choices'][0]['message']['content']


def write_individual_results(directory, results, model_name):
    output_file = os.path.join(directory, f'{model_name}_results.txt')
    with open(output_file, 'w', encoding='utf-8') as f:
        for result in results:
            f.write(result + '\n' + '=' * 50 + '\n')
    return output_file


def combine_and_summarize(directory, llama_file, qwen_file, qwen_url):
    combined_content = ""

    # 读取llama和qwen的结果文件
    with open(llama_file, 'r', encoding='utf-8') as f:
        combined_content += f.read()

    with open(qwen_file, 'r', encoding='utf-8') as f:
        combined_content += f.read()

    # 使用qwen模型进行汇总处理
    summary_prompt = """
    1. 删除包含“配置信息未提供”等无用信息的部分。
    """
    result_summary = send_to_model(qwen_url, "qwen", summary_prompt, combined_content)

    if result_summary:
        summary_file = os.path.join(directory, 'final_summary_combined.txt')
        with open(summary_file, 'w', encoding='utf-8') as f:
            f.write(result_summary)
        print(f"汇总结果保存至: {summary_file}")
    else:
        print("汇总处理失败")


def main(directory):
    start_time = time.time()

    all_contexts = []

    for file_path, content in read_files(directory):
        contexts = extract_context(content, file_path)
        all_contexts.extend(contexts)

    # 将匹配到的内容写入文件
    matched_file = write_to_file(directory, all_contexts)

    results_llama = []
    results_qwen = []

    with open(matched_file, 'r', encoding='utf-8') as f:
        content = f.read()

    analysis_prompt = """
    1. Ignore lines starting with #, //, /**, or <!--.
    2. Exclude commented lines.
    3. Extract configuration info for: MySQL, Redis, Elasticsearch, MongoDB, Kafka, RocketMQ, RabbitMQ, EMQ, MQTT, Nacos, PostgreSQL, Doris, HBase, InfluxDB, Azkaban, SLS, ClickHouse, MSE, DataWorks, Neo4j, HTTP, HTTPS, GitLab, Jenkins.
    4. Focus on URLs, usernames, passwords, hosts, ports, and database names.
    5. Extract the following attributes:
       - Username
       - Password
       - Host
       - Port
       - Database Name
       - URL or Connection String
    6. Look for configuration patterns like key-value pairs and environment variables.
    7. Ensure extracted values are not in commented sections.
    8. Extract all distinct configurations.
    9. Handle different configuration formats (JSON, YAML, dictionaries, env variables).
    10. Delete sections containing “**配置信息未直接提供**” or similar useless content.
    """

    # 分别调用llama和qwen模型
    result_llama = send_to_model("http://1.1.1.1:8000/v1/chat/completions", "llama", analysis_prompt, content)
    result_qwen = send_to_model("http://1.1.1.1:8001/v1/chat/completions", "qwen", analysis_prompt, content)

    if result_llama:
        results_llama.append(result_llama)
    if result_qwen:
        results_qwen.append(result_qwen)

    # 分别保存llama和qwen的结果到不同文件
    llama_file = write_individual_results(directory, results_llama, "llama")
    qwen_file = write_individual_results(directory, results_qwen, "qwen")

    # 汇总llama和qwen的结果
    combine_and_summarize(directory, llama_file, qwen_file, "http://1.1.1.1:8001/v1/chat/completions")

    end_time = time.time()
    total_duration = end_time - start_time
    print(f"总耗时: {total_duration:.2f} 秒")


if __name__ == "__main__":
    main("/Users/jixing/PycharmProjects/AIOps-utils/Athena_Legacy")

7月7日,收到来自CDN群的告警,当天带宽峰值较往日上涨600倍,进一步发现是某一宣传视频被反复下载,下载用户的IP集中,都是来自山西联通ADSL的一个C段。20240807163117.jpg
通过带宽比较,6日开始就有下载,时间晚上八点开始,一直持续到夜间十一点。至此,初步的用户画像如下

  • 恶意用户IP集中,都是来自山西联通adsl用户。221.205.169段
  • 时间点固定20:00-23:00
  • 下载大文件,我们是一段100MB的宣传视频。每3-5秒下载一次
  • 屏蔽IP后,会更换IP,不断变化UA进行下载

两天账单被刷了

2万!😓

至此,没想明白这种损人不利己的行为。直到看到v2的一篇类似帖子(https://www.v2ex.com/t/1055510#reply27),pcdn用户手动制造下行流量,伪装成正常用户,妄图躲过运营商审查。
20240807160710.jpg

鉴于对公司利益造成实际损失,联合安全同事决定报警,起码提醒对方不要再有此行为。提供资料后警方不予立案,理由如下

  • 宣传视频本属于公开资源
  • 源站当时未宕机

对我们提出的经济损失不予认可。不理解但尊重!!最终因为这一两例“老鼠屎”用户,全局限制了下载速度

背景:
位于阿里云的kubernetes集群,通过ingress svc暴露,类型为ClusterIP。某天在对集群内大量缩容节点时,马上出现了大量的502、504报警
500告警.jpg

以为外部密集请求导致后端服务受影响,从监控观察并无异常,联想到最近的变更,怕不是缩容导致?正在排查的过程中,服务恢复。事后通过工单确认
ClusterIP类型的SLB后端的rs被移除时,SLB的操作就是静默丢包,也就是该SLB对任何发过来的tcp包都会默认丢弃,DROP并不会响应

  • 如果此时此链接是空闲链接,就会等待SLB设置的空闲链接超时时间,超过这个时间后,SLB断开链接
  • 如果此时此链接还有请求,由于SLB不会回ack包,客户端就会超时重传,重传的时间就取决于客户端或者内核的超时设置,比如内核ack默认会进行15次重传(net.ipv4.tcp_retries2=15),累加起来大概是924秒,JDBC URL支持设置SocketTimeout、ConnectTimeout,会在这个超时时间后断开重连

文档描述
https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/add-annotations-to-the-yaml-file-of-a-service-to-configure-clb-instances
注解.jpg

最终解法

  • 代码中判断,失败重新建立连接
  • 在ingress中开启上述注解,缩短时间。并在谷值时缩容

早上看到一个粗看起来很简单的问题,“如何在crontab中增加每7分钟循环执行的任务”,下意识想不就是 */7。这么简单的问题怎么一堆回答。
先上结论,比如每7分钟,我10:40分添加的,

  • 第一次执行是在10:42,而不是下意识的10:47
  • 10:56最后一次执行,下一次执行是在11:00,而不是想象中的11:03

cron.jpg
也就破坏了需求中“每7分钟”,解决办法是每分钟执行,在脚本中判断当前时间

# 获取当前时间的分钟数
current_minute=$(date +"%M")

# 判断分钟数是否是7的倍数
if (( current_minute % 7 == 0 )); then
  # 如果是7的倍数,执行任务
  echo $(date +"%Y-%m-%d %H:%M:%S") >> /tmp/timestamp.txt
fi

背景:

我们有一款私有化部署系统,服务于用户购买的智能终端(简单理解为顺丰小哥手持的扫码枪)。周六接到端开发工程师反馈,此批设备续航异常,理论数据两周,但实际只有三至四天。
经过监测电量、抓包手段,电流的峰谷交替约为20秒电量消耗.jpg
端上抓包也显示每20秒收到来自服务器的keepalive报文,此原因导致设备无法休眠!

排查过程:

  1. 首先去服务端抓包,很清晰也能看到每20秒。当我看到抓包结果时,马上自信的答复,这是端上发来的keepalive包呀,肯定端上问题,不查端查服务器干嘛- -!keepalive包.jpg
  2. battle,端开发工程师回怼,“我抓包是从服务器端返回的”,仔细一看还真是。这时候懵圈,证据显示都是对方发来的,且正确响应
  3. 没头绪时,在想经历的中间环节。智能终端---aws NLB---mqtt服务器,NLB配置了标准的tls,卸载证书后转发到mqtt,功能测试都正常,且NLB用了这么久,不应该有问题
  4. 难道是运营商或者网络设备发送?想想不可能,tcp无法伪造IP,持续没头绪
  5. 中间环节只有NLB,进一步排查发现aws的NLB有区别于其他云的独特“机制”aws机制.jpg
  6. 接下来就是验证EIP直接暴露,问题消失。问题确认

总结:

aws NLB产品如果配置了tls监听,会主动20秒为周期发送keepalive!!!

客户端发送Keep-Alive包
AWS NLB行为时间线

时间点行为描述
第0秒客户端发送Keep-Alive包,NLB接收到并立即返回。
第20秒NLB发送Keep-Alive包到前端和后端。
第40秒NLB再次发送Keep-Alive包到前端和后端。
第60秒客户端再次发送Keep-Alive包,NLB接收到并重置计时。
第80秒NLB发送Keep-Alive包到前端和后端。
...重复每20秒发送Keep-Alive包的过程,直到客户端停止发送。

阿里云NLB行为时间线

时间点行为描述
第0秒客户端发送Keep-Alive包,NLB接收到并保持连接。
第60秒客户端再次发送Keep-Alive包,NLB接收到并保持连接。
第120秒客户端再次发送Keep-Alive包,NLB接收到并保持连接。
...重复每60秒发送Keep-Alive包的过程,直到客户端停止发送。

客户端不发送Keep-Alive包
AWS NLB行为时间线

时间点行为描述
第0秒客户端与NLB建立连接。
第350秒没有数据包发送,NLB关闭连接并发送TCP RST包。
第351秒连接关闭,客户端无法继续发送数据。

阿里云NLB行为时间线

时间点行为描述
第0秒客户端与NLB建立连接。
第900秒没有数据包发送,NLB关闭连接。
第901秒连接关闭,客户端无法继续发送数据。

在上一篇文档中实现了检查单台服务器故障的典型排错场景。此次我们加大难度
一、排查链路中故障,识别南北向流量走向并给出排查结果

  • 难点

    • 模型无法理解私有环境
    • 给出的建议具备安全风险
  • 思路

    • 结合提示词,使用function call功能来准确获取配置
    • 新增the_supervisor角色,对planer的建议安全过滤

二、与真实用户交流,给出域名申请建议并检测是否可用

  • 难点

    • 引导用户使用合规的子域名
  • 思路

    • 提示词约定规范,使用socket库来判断可用性

整体难点,多agent执行顺序,“技能绑定”,来看效果。图1为用户与gatekeeper探讨需求pic1.jpg
图2为agent建议用户使用的解析记录pic2.jpg
图3为正确路由南北向流量问题,并使用对应function判断pic3.jpg

关键代码片段
pic4.jpg

探索大模型在运维工作中的方向,此篇主要讲故障排查。是“Autogen 运维排错实践-复杂案例”的进一步整合,改进如下

  • 通过跳板机,不需要在目标机器安装agent,零侵入
  • 入口统一,集成在运维系统
  • 模型自由切换,GPT-4/Claude/tongyi等等

效果

用户在资产中选择目标机器
host_management.png
描述故障,选择策略(自动执行、逐步询问),点击执行
start_tr.png
输出结果
end_tr.png

方案设计

利用堡垒机与所有目标机器互通,将aiagent部署在此。通过提示词确认专精方向、连接方式。后端使用Django开启websocket,前端使用xterm.js模拟终端
topology.png

重点

  • Xterm.js学习曲线陡峭,捕获中文、英文、空格、回退,快捷键等均需要自定义。在即将完成时看到有封装更简单的项目webssh
  • Autogen中与openai通信使用了api.openai.com,改对应库中的域名至代理域名
  • websocket模式需要配置asgi使用,加载静态文件有差别。consumers.py和routing.py需自定义
  • AIagent中提示词需要明确,注意模型的上下文限制,通过提示词截取部分结果

- 阅读剩余部分 -

模型仅具备各领域的通用知识,对于垂类仍有进步空间,这也是医疗、政务类模型出现的原因。我们在尝试AIagent时发现模型并不够聪明,对于安装性能分析工具,vim前后台等问题无法进展到下一步,详见 Autogen 运维排错实践-复杂案例。此次尝试使用偏运维领域的ServerFault,爬取经过人工审核的有效答案来微调模型,观察效果。简言之,教模型所不擅长

步骤

  1. 爬取ServerFault,筛选有效问答
  2. 微调模型
  3. AutoGen配置微调后模型

先看效果,根据采集到的数据,统计出ServerFault热门词云
ciyun-new.jpg

爬虫

筛选逻辑,根据Active状态&前500页&作者vote过的问题,分别记录问题链接、标题、内容、发布时间、更新时间、被查看总数、投票总数;答案内容、得分9个字段,两张表通过外键关联

CREATETABLE Posts (
    PostID INTEGERPRIMARYKEY,
    PostLink TEXTNOTNULL,
    Title TEXTNOTNULL,
    PostContent TEXTNOTNULL,
    PostTime TEXTNOTNULL,-- ISO8601 strings ("YYYY-MM-DD HH:MM:SS.SSS")
    ModifyTime TEXTNOTNULL,
    ViewCount INTEGERNOTNULL,
    VoteCount INTEGERNOTNULL
);
CREATETABLE Answers (
    AnswerID INTEGERPRIMARYKEY,
    PostID INTEGER,
    AnswerContent TEXTNOTNULL,
    VoteCount INTEGERNOTNULL,
FOREIGNKEY(PostID)REFERENCES Posts(PostID)

);NO;

经过控制爬虫速率,切换代理地址,共采集问题、答案数

数量
Posts6681
Answers16253

VoteCount分布

0-100101-200201-300301-400401-500>500
Posts627885321315
Answers15643150311678

- 阅读剩余部分 -

让查数据这件事,不再是高高在上、遥不可及的技能,而是人人都能玩得转,妙趣横生的小技巧。比如“我的店上周赚了多少钱?哪个商品即将售罄?这个月卖的最多的商品是啥?”,下一秒,答案就像变魔术一样蹦出来。直接看效果

流程如下,

  • 前端使用gradio,采集麦克风声音
  • 后端收到声音,使用openai的speech-to-text
  • 文字通过dbgpt(链接各种自定义模型),转换为sql进行数据库查询
  • 返回对应数据

关键代码

- 阅读剩余部分 -