CrewAI+FastAPI的Pipelines功能实现多CrewAI工作流以及Flows功能实现复杂工作流

目录

一、项目简介和项目结构

crewAI的Pipelines功能实现多CrewAI工作流。

二、代码分析

1、main.py

python 复制代码
# 导入python标准库
import asyncio
import os
import sys
import re
import uuid
import time
import json
import asyncio
import uvicorn
# 导入第三方库
from contextlib import asynccontextmanager
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, StreamingResponse
# from langchain_openai import ChatOpenAI
from crewai import LLM as ChatOpenAI
# 导入本应用程序提供的方法
from crew import CrewtestprojectCrew
from utils.myLLM import my_llm



# 设置OpenAI的大模型的参数  Task中设置输出为:output_json时,需要用到默认的大模型
os.environ["OPENAI_API_BASE"] = "https://api.wlai.vip/v1"
os.environ["OPENAI_API_KEY"] = "sk-Fk4R8G56aHVTjlvhf0KeRJrVaBRweTSGVne00mcvwp9jM3fP"
os.environ["OPENAI_MODEL_NAME"] = "gpt-4o-mini"

# 谷歌搜索引擎
SERPER_API_KEY = "88dc7e3ad5a6547d8b1e753a7e4095e7d47ef345"

# 初始化LLM模型
llm = None
# API服务设置相关  根据自己的实际情况进行调整
PORT = 8012  # 服务访问的端口
# Agent所使用大模型类型 openai:调用gpt大模型;oneapi:调用非gpt大模型;ollama:调用本地大模型
LLM_TYPE = "openai"



# 定义Message类
class RequestMessage(BaseModel):
    role: str
    customer_domain: str
    project_description: str
class ResponseMessage(BaseModel):
    role: str
    content: str
# 定义ChatCompletionRequest类
class ChatCompletionRequest(BaseModel):
    messages: List[RequestMessage]
    stream: Optional[bool] = False
# 定义ChatCompletionResponseChoice类
class ChatCompletionResponseChoice(BaseModel):
    index: int
    message: ResponseMessage
    finish_reason: Optional[str] = None
# 定义ChatCompletionResponse类
class ChatCompletionResponse(BaseModel):
    id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}")
    object: str = "chat.completion"
    created: int = Field(default_factory=lambda: int(time.time()))
    choices: List[ChatCompletionResponseChoice]
    system_fingerprint: Optional[str] = None

# 运行pipeline
async def runPipeline(input_data):
    global llm
    results = await CrewtestprojectCrew(llm).my_pipeline().kickoff(inputs=input_data)
    return results


# 定义了一个异步函数lifespan,它接收一个FastAPI应用实例app作为参数。这个函数将管理应用的生命周期,包括启动和关闭时的操作
# 函数在应用启动时执行一些初始化操作
# 函数在应用关闭时执行一些清理操作
# @asynccontextmanager 装饰器用于创建一个异步上下文管理器,它允许在yield之前和之后执行特定的代码块,分别表示启动和关闭时的操作
@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    # 申明引用全局变量,在函数中被初始化,并在整个应用中使用
    global LLM_TYPE, llm
    global SERPER_API_KEY
    try:
        print("正在初始化模型")
        # 设置google搜索引擎
        os.environ["SERPER_API_KEY"] = SERPER_API_KEY
        # 根据MODEL_TYPE选择初始化对应的模型,默认使用gpt大模型
        llm = my_llm(LLM_TYPE)
        print("LLM初始化完成")

    except Exception as e:
        print(f"初始化过程中出错: {str(e)}")
        # raise 关键字重新抛出异常,以确保程序不会在错误状态下继续运行
        raise

    # yield 关键字将控制权交还给FastAPI框架,使应用开始运行
    # 分隔了启动和关闭的逻辑。在yield 之前的代码在应用启动时运行,yield 之后的代码在应用关闭时运行
    yield
    # 关闭时执行
    print("正在关闭...")


# lifespan 参数用于在应用程序生命周期的开始和结束时执行一些初始化或清理工作
app = FastAPI(lifespan=lifespan)


# POST请求接口,与大模型进行知识问答
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest):
    if not llm:
        print("服务未初始化")
        raise HTTPException(status_code=500, detail="服务未初始化")
    try:
        # print(f"收到聊天完成请求: {request}")
        print(f"已经进入到请求中")
        customer_domain = request.messages[-1].customer_domain
        project_description = request.messages[-1].project_description
        print(f"接收到的信息customer_domain: {customer_domain},接收到的信息project_description: {project_description}")
        # 构造数据
        input_data = [{"customer_domain": customer_domain,"project_description": project_description}]
        # 运行pipeline
        results = await CrewtestprojectCrew(llm).my_pipeline().kickoff(inputs=input_data)
        for result in results:
            # 将返回的数据转成string类型
            formatted_response = str(result)
            print(f"LLM最终回复结果: {formatted_response}")

            # 处理流式响应
            if request.stream:
                # 定义一个异步生成器函数,用于生成流式数据
                async def generate_stream():
                    # 为每个流式数据片段生成一个唯一的chunk_id
                    chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
                    # 将格式化后的响应按行分割
                    lines = formatted_response.split('\n')
                    # 历每一行,并构建响应片段
                    for i, line in enumerate(lines):
                        # 创建一个字典,表示流式数据的一个片段
                        chunk = {
                            "id": chunk_id,
                            "object": "chat.completion.chunk",
                            "created": int(time.time()),
                            # "model": request.model,
                            "choices": [
                                {
                                    "index": 0,
                                    "delta": {"content": line + '\n'}, # if i > 0 else {"role": "assistant", "content": ""},
                                    "finish_reason": None
                                }
                            ]
                        }
                        # 将片段转换为JSON格式并生成
                        yield f"{json.dumps(chunk)}\n"
                        # 每次生成数据后,异步等待0.5秒
                        await asyncio.sleep(0.5)
                    # 生成最后一个片段,表示流式响应的结束
                    final_chunk = {
                        "id": chunk_id,
                        "object": "chat.completion.chunk",
                        "created": int(time.time()),
                        "choices": [
                            {
                                "index": 0,
                                "delta": {},
                                "finish_reason": "stop"
                            }
                        ]
                    }
                    yield f"{json.dumps(final_chunk)}\n"

                # 返回fastapi.responses中StreamingResponse对象,流式传输数据
                # media_type设置为text/event-stream以符合SSE(Server-SentEvents) 格式
                return StreamingResponse(generate_stream(), media_type="text/event-stream")
            # 处理非流式响应处理
            else:
                response = ChatCompletionResponse(
                    choices=[
                        ChatCompletionResponseChoice(
                            index=0,
                            message=ResponseMessage(role="assistant", content=formatted_response),
                            finish_reason="stop"
                        )
                    ]
                )
                # print(f"发送响应内容: \n{response}")
                # 返回fastapi.responses中JSONResponse对象
                # model_dump()方法通常用于将Pydantic模型实例的内容转换为一个标准的Python字典,以便进行序列化
                return JSONResponse(content=response.model_dump())

    except Exception as e:
        print(f"处理聊天完成时出错:\n\n {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))



if __name__ == "__main__":
    print(f"在端口 {PORT} 上启动服务器")
    # uvicorn是一个用于运行ASGI应用的轻量级、超快速的ASGI服务器实现
    # 用于部署基于FastAPI框架的异步PythonWeb应用程序
    uvicorn.run(app, host="0.0.0.0", port=PORT)

2、crew.py

python 复制代码
# 核心功能:在CrewAI中定义Agent和Task,并通过Crew来管理这些Agent和Task的执行流程

# 导入第三方库
from crewai import Agent, Crew, Process, Task, Pipeline
from crewai.project import CrewBase, agent, crew, task, pipeline
from crewai_tools import SerperDevTool, ScrapeWebsiteTool
# 导入本应用程序提供的方法
from utils.models import MarketStrategy, CampaignIdea, Copy



# 定义了一个CrewtestprojectCrew类并应用了@CrewBase装饰器初始化项目
# 这个类代表一个完整的CrewAI项目
@CrewBase
class CrewtestprojectCrew():

	# agents_config和tasks_config分别指向agent和task的配置文件,存放在config目录下
	agents_config = 'config/agents.yaml'
	tasks_config = 'config/tasks.yaml'

	def __init__(self, model):
		# Agent使用的大模型
		self.model = model

	# 通过@agent装饰器定义一个函数researcher,返回一个Agent实例
	# 该代理读取agents_config中的researcher配置
	# 参数verbose=True用于输出调试信息
	# 参数tools=[MyCustomTool()] 可以加载自定义工具
	@agent
	def lead_market_analyst(self) -> Agent:
		return Agent(
			config=self.agents_config['lead_market_analyst'],
			verbose=True,
			llm=self.model,
			tools=[SerperDevTool(), ScrapeWebsiteTool()],
		)

	@agent
	def chief_marketing_strategist(self) -> Agent:
		return Agent(
			config=self.agents_config['chief_marketing_strategist'],
			verbose=True,
			llm=self.model,
			tools=[SerperDevTool(), ScrapeWebsiteTool()],
		)

	@agent
	def creative_content_creator(self) -> Agent:
		return Agent(
			config=self.agents_config['creative_content_creator'],
			verbose=True,
			llm=self.model
		)


	# 通过@task装饰器定义research_task,返回一个Task实例
	# 配置文件为tasks.yaml中的research_task部分
	@task
	def research_task(self) -> Task:
		return Task(
			config=self.tasks_config['research_task'],
		)

	@task
	def project_understanding_task(self) -> Task:
		return Task(
			config=self.tasks_config['project_understanding_task'],

		)

	@task
	def marketing_strategy_task(self) -> Task:
		return Task(
			config=self.tasks_config['marketing_strategy_task'],
			output_json=MarketStrategy
		)

	@task
	def campaign_idea_task(self) -> Task:
		return Task(
			config=self.tasks_config['campaign_idea_task'],
			output_json=CampaignIdea

		)

	@task
	def copy_creation_task(self) -> Task:
		return Task(
			config=self.tasks_config['copy_creation_task'],
			context=[self.marketing_strategy_task(), self.campaign_idea_task()],
			output_json=Copy
		)


	# Crew类将agent和task组合成一个执行队列,并根据指定的执行流程进行任务调度
	# 通过@crew装饰器定义crew,创建一个Crew实例
	# agents=self.agents和tasks=self.tasks分别自动获取@agent和@task装饰器生成的agent和task
	# process=Process.sequential指定agent执行顺序为顺序执行模式
	# process=Process.hierarchical指定agent执行顺序为层次化执行
	@crew
	def crewA(self) -> Crew:
		return Crew(
			agents=[self.lead_market_analyst()],
			tasks=[self.research_task()],
			process=Process.sequential,
			verbose=True
		)

	@crew
	def crewB(self) -> Crew:
		return Crew(
			agents=[self.chief_marketing_strategist(), self.creative_content_creator()],
			tasks=[self.project_understanding_task(), self.marketing_strategy_task(), self.campaign_idea_task(), self.copy_creation_task()],
			process=Process.sequential,
			verbose=True
		)


	# Pipeline类将crew组合成一个可执行的流水线
	# 通过@pipeline装饰器定义pipeline,创建一个Pipeline实例
	# stages:编排crew的一个独立部分,可以是连续的crew编排,也可以是并行的crew编排
	@pipeline
	def my_pipeline(self) -> Pipeline:
		return Pipeline(
			stages = [self.crewA(), self.crewB()]
		)

3、工具类

3.1 model.py:
python 复制代码
# 核心功能:定义数据模型,使用Pydantic库来进行数据验证和类型检查


from typing import List
from pydantic import BaseModel, Field

class MarketStrategy(BaseModel):
	"""Market strategy model"""
	name: str = Field(..., description="市场战略名称")
	tatics: List[str] = Field(..., description="市场战略中使用的战术清单")
	channels: List[str] = Field(..., description="市场战略中使用的渠道清单")
	KPIs: List[str] = Field(..., description="市场战略中使用的关键绩效指标清单")

class CampaignIdea(BaseModel):
	"""Campaign idea model"""
	name: str = Field(..., description="活动创意名称")
	description: str = Field(..., description="Description of the campaign idea")
	audience: str = Field(..., description="活动创意说明")
	channel: str = Field(..., description="活动创意渠道")

class Copy(BaseModel):
	"""Copy model"""
	title: str = Field(..., description="副本标题")
	body: str = Field(..., description="正文")
3.2 myLLM.py
python 复制代码
import os
from crewai import LLM as ChatOpenAI

# 模型全局参数配置  根据自己的实际情况进行调整
# openai模型相关配置 根据自己的实际情况进行调整
OPENAI_API_BASE = "https://api.wlai.vip/v1"
OPENAI_CHAT_API_KEY = "sk-Fk4R8G56aHVTjlvhf0KeRJrVaBRweTSGVne00mcvwp9jM3fP"
OPENAI_CHAT_MODEL = "gpt-4o-mini"
# 非gpt大模型相关配置(oneapi方案 通义千问为例) 根据自己的实际情况进行调整
ONEAPI_API_BASE = "http://139.224.72.218:3000/v1"
ONEAPI_CHAT_API_KEY = "sk-0FxX9ncd0yXjTQF877Cc9dB6B2F44aD08d62805715821b85"
ONEAPI_CHAT_MODEL = "qwen-max"
# 本地大模型相关配置(Ollama方案 llama3.1:latest为例) 根据自己的实际情况进行调整
OLLAMA_API_BASE = "http://localhost:11434/v1"
OLLAMA_CHAT_API_KEY = "ollama"
OLLAMA_CHAT_MODEL = "llama3.1:latest"

# 定函数 模型初始化
def my_llm(llmType):
    if llmType == "oneapi":
        # 实例化一个oneapi客户端对象
        llm = ChatOpenAI(
            base_url=ONEAPI_API_BASE,
            api_key=ONEAPI_CHAT_API_KEY,
            model=ONEAPI_CHAT_MODEL,  # 本次使用的模型
            temperature=0.7,  # 发散的程度
            # timeout=None,# 服务请求超时
            # max_retries=2,# 失败重试最大次数
        )
    elif llmType == "ollama":
        # 实例化一个ChatOpenAI客户端对象
        os.environ["OPENAI_API_KEY"] = "NA"
        llm = ChatOpenAI(
            base_url=OLLAMA_API_BASE,  # 请求的API服务地址
            api_key=OLLAMA_CHAT_API_KEY,  # API Key
            model=OLLAMA_CHAT_MODEL,  # 本次使用的模型
            temperature=0.7,  # 发散的程度
            # timeout=None,# 服务请求超时
            # max_retries=2,# 失败重试最大次数
        )
    else:
        # 实例化一个ChatOpenAI客户端对象
        llm = ChatOpenAI(
            base_url=OPENAI_API_BASE,  # 请求的API服务地址
            api_key=OPENAI_CHAT_API_KEY,  # API Key
            model=OPENAI_CHAT_MODEL,  # 本次使用的模型
            # temperature=0.7,# 发散的程度,一般为0
            # timeout=None,# 服务请求超时
            # max_retries=2,# 失败重试最大次数
        )
    return llm

总结:

1、问题分析

  • (1)将crewai由0.55.2升级为0.74.2,crewai-tools由0.12.0升级为0.13.2(截止2024.10.19),并解决因版本升级出现的问题
  • (2)改写crew.py代码实现crewai的pipelines功能
  • (3)将pipelines功能集成到main服务中对外提供API接口服务,进行接口联调测试

2、Piplelines介绍

CrewAI中的pipeline代表一种结构化的工作流程,允许多个crew顺序或并行执行。它提供了一种组织涉及多个stage的复杂流程的方法,其中一个stage的输出可作为后续stage的输入。

三、crewAI的Flows功能

这个可以实现上一个工作流的结果,下一个进行监听后再继续做处理。

3.1 项目结构

3.2 项目代码分析

1、mian.py
python 复制代码
# 导入python标准库
import os
import uuid
import time
import json
import asyncio
import uvicorn
# 导入第三方库
from contextlib import asynccontextmanager
from pydantic import BaseModel, Field
from typing import List, Optional
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
# 导入本应用程序提供的方法
from flows import testFlow
from utils.myLLM import my_llm



# Agent所使用大模型类型 openai:调用gpt大模型;oneapi:调用非gpt大模型;ollama:调用本地大模型
LLM_TYPE = "openai"

# 设置OpenAI的大模型的参数  Task中设置输出为:output_json时,需要用到默认的大模型
os.environ["OPENAI_API_BASE"] = "https://api.wlai.vip/v1"
os.environ["OPENAI_API_KEY"] = "sk-dvdCgdO3LSWYgqnMiJR5NqG0eLSTM69yjryjD6LuL3lWkvf3"
os.environ["OPENAI_MODEL_NAME"] = "gpt-4o-mini"

# 谷歌搜索引擎
SERPER_API_KEY = "eb8f2749944a5e5fbcfeae072535f07844b5134e"

# 初始化LLM模型
llm = None

# API服务设置相关  根据自己的实际情况进行调整
PORT = 8012  # 服务访问的端口


# 定义Message类
class RequestMessage(BaseModel):
    role: str
    customer_domain: str
    project_description: str
class ResponseMessage(BaseModel):
    role: str
    content: str
# 定义ChatCompletionRequest类
class ChatCompletionRequest(BaseModel):
    messages: List[RequestMessage]
    stream: Optional[bool] = False
# 定义ChatCompletionResponseChoice类
class ChatCompletionResponseChoice(BaseModel):
    index: int
    message: ResponseMessage
    finish_reason: Optional[str] = None
# 定义ChatCompletionResponse类
class ChatCompletionResponse(BaseModel):
    id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}")
    object: str = "chat.completion"
    created: int = Field(default_factory=lambda: int(time.time()))
    choices: List[ChatCompletionResponseChoice]
    system_fingerprint: Optional[str] = None


# 定义了一个异步函数lifespan,它接收一个FastAPI应用实例app作为参数。这个函数将管理应用的生命周期,包括启动和关闭时的操作
# 函数在应用启动时执行一些初始化操作
# 函数在应用关闭时执行一些清理操作
# @asynccontextmanager 装饰器用于创建一个异步上下文管理器,它允许在yield之前和之后执行特定的代码块,分别表示启动和关闭时的操作
@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    # 申明引用全局变量,在函数中被初始化,并在整个应用中使用
    global LLM_TYPE, llm
    global SERPER_API_KEY
    try:
        print("正在初始化模型")
        # 设置google搜索引擎
        os.environ["SERPER_API_KEY"] = SERPER_API_KEY
        # 根据MODEL_TYPE选择初始化对应的模型,默认使用gpt大模型
        llm = my_llm(LLM_TYPE)
        print("LLM初始化完成")

    except Exception as e:
        print(f"初始化过程中出错: {str(e)}")
        # raise 关键字重新抛出异常,以确保程序不会在错误状态下继续运行
        raise

    # yield 关键字将控制权交还给FastAPI框架,使应用开始运行
    # 分隔了启动和关闭的逻辑。在yield 之前的代码在应用启动时运行,yield 之后的代码在应用关闭时运行
    yield
    # 关闭时执行
    print("正在关闭...")


# lifespan 参数用于在应用程序生命周期的开始和结束时执行一些初始化或清理工作
app = FastAPI(lifespan=lifespan)


# POST请求接口,与大模型进行知识问答
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest):
    if not llm:
        print("服务未初始化")
        raise HTTPException(status_code=500, detail="服务未初始化")
    try:
        # print(f"收到聊天完成请求: {request}")
        print(f"已经进入到请求中")
        customer_domain = request.messages[-1].customer_domain
        project_description = request.messages[-1].project_description
        print(f"接收到的信息customer_domain: {customer_domain},接收到的信息project_description: {project_description}")
        # 构造数据
        inputData = {"customer_domain": customer_domain, "project_description": project_description}
        # 运行flow
        result = await testFlow(llm, inputData).kickoff()
        # 将返回的数据转成string类型
        formatted_response = str(result)
        print(f"LLM最终回复结果: {formatted_response}")

        # 处理流式响应
        if request.stream:
            # 定义一个异步生成器函数,用于生成流式数据
            async def generate_stream():
                # 为每个流式数据片段生成一个唯一的chunk_id
                chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
                # 将格式化后的响应按行分割
                lines = formatted_response.split('\n')
                # 历每一行,并构建响应片段
                for i, line in enumerate(lines):
                    # 创建一个字典,表示流式数据的一个片段
                    chunk = {
                        "id": chunk_id,
                        "object": "chat.completion.chunk",
                        "created": int(time.time()),
                        # "model": request.model,
                        "choices": [
                            {
                                "index": 0,
                                "delta": {"content": line + '\n'}, # if i > 0 else {"role": "assistant", "content": ""},
                                "finish_reason": None
                            }
                        ]
                    }
                    # 将片段转换为JSON格式并生成
                    yield f"{json.dumps(chunk)}\n"
                    # 每次生成数据后,异步等待0.5秒
                    await asyncio.sleep(0.5)
                # 生成最后一个片段,表示流式响应的结束
                final_chunk = {
                    "id": chunk_id,
                    "object": "chat.completion.chunk",
                    "created": int(time.time()),
                    "choices": [
                        {
                            "index": 0,
                            "delta": {},
                            "finish_reason": "stop"
                        }
                    ]
                }
                yield f"{json.dumps(final_chunk)}\n"

            # 返回fastapi.responses中StreamingResponse对象,流式传输数据
            # media_type设置为text/event-stream以符合SSE(Server-SentEvents) 格式
            return StreamingResponse(generate_stream(), media_type="text/event-stream")
        # 处理非流式响应处理
        else:
            response = ChatCompletionResponse(
                choices=[
                    ChatCompletionResponseChoice(
                        index=0,
                        message=ResponseMessage(role="assistant", content=formatted_response),
                        finish_reason="stop"
                    )
                ]
            )
            # print(f"发送响应内容: \n{response}")
            # 返回fastapi.responses中JSONResponse对象
            # model_dump()方法通常用于将Pydantic模型实例的内容转换为一个标准的Python字典,以便进行序列化
            return JSONResponse(content=response.model_dump())

    except Exception as e:
        print(f"处理聊天完成时出错:\n\n {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))



if __name__ == "__main__":
    print(f"在端口 {PORT} 上启动服务器")
    # uvicorn是一个用于运行ASGI应用的轻量级、超快速的ASGI服务器实现
    # 用于部署基于FastAPI框架的异步PythonWeb应用程序
    uvicorn.run(app, host="0.0.0.0", port=PORT)
2、flows.py
python 复制代码
# 导入python标准库
import os
import uuid
import time
import json
import asyncio
import uvicorn
# 导入第三方库
from contextlib import asynccontextmanager
from pydantic import BaseModel, Field
from typing import List, Optional
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse
# 导入本应用程序提供的方法
from flows import testFlow
from utils.myLLM import my_llm



# Agent所使用大模型类型 openai:调用gpt大模型;oneapi:调用非gpt大模型;ollama:调用本地大模型
LLM_TYPE = "openai"

# 设置OpenAI的大模型的参数  Task中设置输出为:output_json时,需要用到默认的大模型
os.environ["OPENAI_API_BASE"] = "https://api.wlai.vip/v1"
os.environ["OPENAI_API_KEY"] = "sk-dvdCgdO3LSWYgqnMiJR5NqG0eLSTM69yjryjD6LuL3lWkvf3"
os.environ["OPENAI_MODEL_NAME"] = "gpt-4o-mini"

# 谷歌搜索引擎
SERPER_API_KEY = "eb8f2749944a5e5fbcfeae072535f07844b5134e"

# 初始化LLM模型
llm = None

# API服务设置相关  根据自己的实际情况进行调整
PORT = 8012  # 服务访问的端口


# 定义Message类
class RequestMessage(BaseModel):
    role: str
    customer_domain: str
    project_description: str
class ResponseMessage(BaseModel):
    role: str
    content: str
# 定义ChatCompletionRequest类
class ChatCompletionRequest(BaseModel):
    messages: List[RequestMessage]
    stream: Optional[bool] = False
# 定义ChatCompletionResponseChoice类
class ChatCompletionResponseChoice(BaseModel):
    index: int
    message: ResponseMessage
    finish_reason: Optional[str] = None
# 定义ChatCompletionResponse类
class ChatCompletionResponse(BaseModel):
    id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex}")
    object: str = "chat.completion"
    created: int = Field(default_factory=lambda: int(time.time()))
    choices: List[ChatCompletionResponseChoice]
    system_fingerprint: Optional[str] = None


# 定义了一个异步函数lifespan,它接收一个FastAPI应用实例app作为参数。这个函数将管理应用的生命周期,包括启动和关闭时的操作
# 函数在应用启动时执行一些初始化操作
# 函数在应用关闭时执行一些清理操作
# @asynccontextmanager 装饰器用于创建一个异步上下文管理器,它允许在yield之前和之后执行特定的代码块,分别表示启动和关闭时的操作
@asynccontextmanager
async def lifespan(app: FastAPI):
    # 启动时执行
    # 申明引用全局变量,在函数中被初始化,并在整个应用中使用
    global LLM_TYPE, llm
    global SERPER_API_KEY
    try:
        print("正在初始化模型")
        # 设置google搜索引擎
        os.environ["SERPER_API_KEY"] = SERPER_API_KEY
        # 根据MODEL_TYPE选择初始化对应的模型,默认使用gpt大模型
        llm = my_llm(LLM_TYPE)
        print("LLM初始化完成")

    except Exception as e:
        print(f"初始化过程中出错: {str(e)}")
        # raise 关键字重新抛出异常,以确保程序不会在错误状态下继续运行
        raise

    # yield 关键字将控制权交还给FastAPI框架,使应用开始运行
    # 分隔了启动和关闭的逻辑。在yield 之前的代码在应用启动时运行,yield 之后的代码在应用关闭时运行
    yield
    # 关闭时执行
    print("正在关闭...")


# lifespan 参数用于在应用程序生命周期的开始和结束时执行一些初始化或清理工作
app = FastAPI(lifespan=lifespan)


# POST请求接口,与大模型进行知识问答
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest):
    if not llm:
        print("服务未初始化")
        raise HTTPException(status_code=500, detail="服务未初始化")
    try:
        # print(f"收到聊天完成请求: {request}")
        print(f"已经进入到请求中")
        customer_domain = request.messages[-1].customer_domain
        project_description = request.messages[-1].project_description
        print(f"接收到的信息customer_domain: {customer_domain},接收到的信息project_description: {project_description}")
        # 构造数据
        inputData = {"customer_domain": customer_domain, "project_description": project_description}
        # 运行flow
        result = await testFlow(llm, inputData).kickoff()
        # 将返回的数据转成string类型
        formatted_response = str(result)
        print(f"LLM最终回复结果: {formatted_response}")

        # 处理流式响应
        if request.stream:
            # 定义一个异步生成器函数,用于生成流式数据
            async def generate_stream():
                # 为每个流式数据片段生成一个唯一的chunk_id
                chunk_id = f"chatcmpl-{uuid.uuid4().hex}"
                # 将格式化后的响应按行分割
                lines = formatted_response.split('\n')
                # 历每一行,并构建响应片段
                for i, line in enumerate(lines):
                    # 创建一个字典,表示流式数据的一个片段
                    chunk = {
                        "id": chunk_id,
                        "object": "chat.completion.chunk",
                        "created": int(time.time()),
                        # "model": request.model,
                        "choices": [
                            {
                                "index": 0,
                                "delta": {"content": line + '\n'}, # if i > 0 else {"role": "assistant", "content": ""},
                                "finish_reason": None
                            }
                        ]
                    }
                    # 将片段转换为JSON格式并生成
                    yield f"{json.dumps(chunk)}\n"
                    # 每次生成数据后,异步等待0.5秒
                    await asyncio.sleep(0.5)
                # 生成最后一个片段,表示流式响应的结束
                final_chunk = {
                    "id": chunk_id,
                    "object": "chat.completion.chunk",
                    "created": int(time.time()),
                    "choices": [
                        {
                            "index": 0,
                            "delta": {},
                            "finish_reason": "stop"
                        }
                    ]
                }
                yield f"{json.dumps(final_chunk)}\n"

            # 返回fastapi.responses中StreamingResponse对象,流式传输数据
            # media_type设置为text/event-stream以符合SSE(Server-SentEvents) 格式
            return StreamingResponse(generate_stream(), media_type="text/event-stream")
        # 处理非流式响应处理
        else:
            response = ChatCompletionResponse(
                choices=[
                    ChatCompletionResponseChoice(
                        index=0,
                        message=ResponseMessage(role="assistant", content=formatted_response),
                        finish_reason="stop"
                    )
                ]
            )
            # print(f"发送响应内容: \n{response}")
            # 返回fastapi.responses中JSONResponse对象
            # model_dump()方法通常用于将Pydantic模型实例的内容转换为一个标准的Python字典,以便进行序列化
            return JSONResponse(content=response.model_dump())

    except Exception as e:
        print(f"处理聊天完成时出错:\n\n {str(e)}")
        raise HTTPException(status_code=500, detail=str(e))



if __name__ == "__main__":
    print(f"在端口 {PORT} 上启动服务器")
    # uvicorn是一个用于运行ASGI应用的轻量级、超快速的ASGI服务器实现
    # 用于部署基于FastAPI框架的异步PythonWeb应用程序
    uvicorn.run(app, host="0.0.0.0", port=PORT)
3、contentCreatorCrew.py
python 复制代码
# 核心功能:在CrewAI中定义Agent和Task,并通过Crew来管理这些Agent和Task的执行流程

# 导入第三方库
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool, ScrapeWebsiteTool
# 导入本应用程序提供的方法
from utils.models import MarketStrategy, CampaignIdea, Copy


# 定义了一个CrewtestprojectCrew类并应用了@CrewBase装饰器初始化项目
# 这个类代表一个完整的CrewAI项目
@CrewBase
class contentCreatorCrew():

	# agents_config和tasks_config分别指向agent和task的配置文件,存放在config目录下
	agents_config = 'config/agents.yaml'
	tasks_config = 'config/tasks.yaml'

	def __init__(self, model):
		# Agent使用的大模型
		self.model = model

	# 通过@agent装饰器定义一个函数researcher,返回一个Agent实例
	# 该代理读取agents_config中的researcher配置
	# 参数verbose=True用于输出调试信息
	# 参数tools=[MyCustomTool()] 可以加载自定义工具
	@agent
	def chief_marketing_strategist(self) -> Agent:
		return Agent(
			config=self.agents_config['chief_marketing_strategist'],
			verbose=True,
			llm=self.model,
			tools=[SerperDevTool(), ScrapeWebsiteTool()],
		)

	@agent
	def creative_content_creator(self) -> Agent:
		return Agent(
			config=self.agents_config['creative_content_creator'],
			verbose=True,
			llm=self.model
		)


	# 通过@task装饰器定义research_task,返回一个Task实例
	# 配置文件为tasks.yaml中的research_task部分
	@task
	def project_understanding_task(self) -> Task:
		return Task(
			config=self.tasks_config['project_understanding_task'],

		)

	@task
	def marketing_strategy_task(self) -> Task:
		return Task(
			config=self.tasks_config['marketing_strategy_task'],
			output_json=MarketStrategy
		)

	@task
	def campaign_idea_task(self) -> Task:
		return Task(
			config=self.tasks_config['campaign_idea_task'],
			output_json=CampaignIdea

		)

	@task
	def copy_creation_task(self) -> Task:
		return Task(
			config=self.tasks_config['copy_creation_task'],
			context=[self.marketing_strategy_task(), self.campaign_idea_task()],
			output_json=Copy
		)


	# Crew类将agent和task组合成一个执行队列,并根据指定的执行流程进行任务调度
	# 通过@crew装饰器定义crew,创建一个Crew实例
	# agents=self.agents和tasks=self.tasks分别自动获取@agent和@task装饰器生成的agent和task
	# process=Process.sequential指定agent执行顺序为顺序执行模式
	# process=Process.hierarchical指定agent执行顺序为层次化执行
	@crew
	def crew(self) -> Crew:
		return Crew(
			agents=self.agents,
			tasks=self.tasks,
			process=Process.sequential,
			verbose=True
		)
4、marketAnalystCrew.py
python 复制代码
# 核心功能:在CrewAI中定义Agent和Task,并通过Crew来管理这些Agent和Task的执行流程

# 导入第三方库
from crewai import Agent, Crew, Process, Task
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool, ScrapeWebsiteTool



# 定义了一个CrewtestprojectCrew类并应用了@CrewBase装饰器初始化项目
# 这个类代表一个完整的CrewAI项目
@CrewBase
class marketAnalystCrew():

	# agents_config和tasks_config分别指向agent和task的配置文件,存放在config目录下
	agents_config = 'config/agents.yaml'
	tasks_config = 'config/tasks.yaml'

	def __init__(self, model):
		# Agent使用的大模型
		self.model = model

	# 通过@agent装饰器定义一个函数researcher,返回一个Agent实例
	# 该代理读取agents_config中的researcher配置
	# 参数verbose=True用于输出调试信息
	# 参数tools=[MyCustomTool()] 可以加载自定义工具
	@agent
	def lead_market_analyst(self) -> Agent:
		return Agent(
			config=self.agents_config['lead_market_analyst'],
			verbose=True,
			llm=self.model,
			tools=[SerperDevTool(), ScrapeWebsiteTool()],
		)


	# 通过@task装饰器定义research_task,返回一个Task实例
	# 配置文件为tasks.yaml中的research_task部分
	@task
	def research_task(self) -> Task:
		return Task(
			config=self.tasks_config['research_task'],
		)


	# Crew类将agent和task组合成一个执行队列,并根据指定的执行流程进行任务调度
	# 通过@crew装饰器定义crew,创建一个Crew实例
	# agents=self.agents和tasks=self.tasks分别自动获取@agent和@task装饰器生成的agent和task
	# process=Process.sequential指定agent执行顺序为顺序执行模式
	# process=Process.hierarchical指定agent执行顺序为层次化执行
	@crew
	def crew(self) -> Crew:
		return Crew(
			agents=self.agents,
			tasks=self.tasks,
			process=Process.sequential,
			verbose=True
		)

这个代码也是比较简单,就是主方法调用两个crewAi的工作流,然后flows集成两个工作流做处理,当一个工作流响应了,另一个工作监听到后拿到结果后继续执行处理。

项目代码地址:

https://github.com/NanGePlus/CrewAITest/tree/main/crewAIWithFlows

flows文档地址:

https://docs.crewai.com/en/concepts/flows

相关推荐
liulilittle14 小时前
ISIF Cloud HKG-B/HKG-C解锁(NSP)
linux·运维·服务器
拾贰_C14 小时前
【无标题】
运维·服务器·数据库·pytorch·python·考研·学习方法
山沐与山14 小时前
【FastAPI】FastAPI RESTful API实战:从接口规范到优雅设计
后端·restful·fastapi
Gofarlic_oms114 小时前
Kisssoft许可证服务器高可用性(HA)集群配置方案
运维·服务器·网络·安全·需求分析·devops
qinyia14 小时前
WisdomSSH解决硬盘直通给飞牛系统时控制器无法绑定的问题
java·linux·服务器
网硕互联的小客服14 小时前
服务器平均响应时间和数据包大小有什么关系?
运维·服务器·网络
oMcLin14 小时前
如何在CentOS 7服务器上通过系统调优提升Redis缓存的吞吐量与响应速度?
服务器·缓存·centos
_w_z_j_14 小时前
Linux----Socket实现UDP简单服务器与客户端程序
linux·运维·服务器
CoderIsArt14 小时前
iSCSI架构中客户端与服务端
服务器·网络·架构