From 992398bca31d7d11b0fb7b0fafee8ae87f95d8f1 Mon Sep 17 00:00:00 2001 From: Song Fuchang Date: Mon, 12 May 2025 17:59:53 +0800 Subject: [PATCH] Feat: Add http api to create, update, or delete agents. (#7515) ### What problem does this PR solve? Hello, we are using ragflow as a backend service, so we need to manage agents from our own frontend. So adding these http APIs to manage agents. The code logic is copied and modified from the `rm` and `save` methods in `api/apps/canvas_app.py`. btw, I found that the `save` method in `canvas_app.py` actually allows to modify an agent to an existing title, so I kept the behavior in the http api. I'm not sure if this is intentional. ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe): --- api/apps/sdk/agent.py | 91 +++++++++++- docs/references/http_api_reference.md | 187 ++++++++++++++++++++++++ docs/references/python_api_reference.md | 129 ++++++++++++++++ sdk/python/ragflow_sdk/ragflow.py | 46 ++++++ 4 files changed, 452 insertions(+), 1 deletion(-) diff --git a/api/apps/sdk/agent.py b/api/apps/sdk/agent.py index a60a645bc..704a3ffcf 100644 --- a/api/apps/sdk/agent.py +++ b/api/apps/sdk/agent.py @@ -14,8 +14,14 @@ # limitations under the License. # +import json +import time +from typing import Any, cast from api.db.services.canvas_service import UserCanvasService -from api.utils.api_utils import get_error_data_result, token_required +from api.db.services.user_canvas_version import UserCanvasVersionService +from api.settings import RetCode +from api.utils import get_uuid +from api.utils.api_utils import get_data_error_result, get_error_data_result, get_json_result, token_required from api.utils.api_utils import get_result from flask import request @@ -37,3 +43,86 @@ def list_agents(tenant_id): desc = True canvas = UserCanvasService.get_list(tenant_id,page_number,items_per_page,orderby,desc,id,title) return get_result(data=canvas) + + +@manager.route("/agents", methods=["POST"]) # noqa: F821 +@token_required +def create_agent(tenant_id: str): + req: dict[str, Any] = cast(dict[str, Any], request.json) + req["user_id"] = tenant_id + + if req.get("dsl") is not None: + if not isinstance(req["dsl"], str): + req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) + + req["dsl"] = json.loads(req["dsl"]) + else: + return get_json_result(data=False, message="No DSL data in request.", code=RetCode.ARGUMENT_ERROR) + + if req.get("title") is not None: + req["title"] = req["title"].strip() + else: + return get_json_result(data=False, message="No title in request.", code=RetCode.ARGUMENT_ERROR) + + if UserCanvasService.query(user_id=tenant_id, title=req["title"]): + return get_data_error_result(message=f"Agent with title {req['title']} already exists.") + + agent_id = get_uuid() + req["id"] = agent_id + + if not UserCanvasService.save(**req): + return get_data_error_result(message="Fail to create agent.") + + UserCanvasVersionService.insert( + user_canvas_id=agent_id, + title="{0}_{1}".format(req["title"], time.strftime("%Y_%m_%d_%H_%M_%S")), + dsl=req["dsl"] + ) + + return get_json_result(data=True) + + +@manager.route("/agents/", methods=["PUT"]) # noqa: F821 +@token_required +def update_agent(tenant_id: str, agent_id: str): + req: dict[str, Any] = {k: v for k, v in cast(dict[str, Any], request.json).items() if v is not None} + req["user_id"] = tenant_id + + if req.get("dsl") is not None: + if not isinstance(req["dsl"], str): + req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False) + + req["dsl"] = json.loads(req["dsl"]) + + if req.get("title") is not None: + req["title"] = req["title"].strip() + + if not UserCanvasService.query(user_id=tenant_id, id=agent_id): + return get_json_result( + data=False, message="Only owner of canvas authorized for this operation.", + code=RetCode.OPERATING_ERROR) + + UserCanvasService.update_by_id(agent_id, req) + + if req.get("dsl") is not None: + UserCanvasVersionService.insert( + user_canvas_id=agent_id, + title="{0}_{1}".format(req["title"], time.strftime("%Y_%m_%d_%H_%M_%S")), + dsl=req["dsl"] + ) + + UserCanvasVersionService.delete_all_versions(agent_id) + + return get_json_result(data=True) + + +@manager.route("/agents/", methods=["DELETE"]) # noqa: F821 +@token_required +def delete_agent(tenant_id: str, agent_id: str): + if not UserCanvasService.query(user_id=tenant_id, id=agent_id): + return get_json_result( + data=False, message="Only owner of canvas authorized for this operation.", + code=RetCode.OPERATING_ERROR) + + UserCanvasService.delete_by_id(agent_id) + return get_json_result(data=True) diff --git a/docs/references/http_api_reference.md b/docs/references/http_api_reference.md index cc3634519..6b1e427ec 100644 --- a/docs/references/http_api_reference.md +++ b/docs/references/http_api_reference.md @@ -3413,3 +3413,190 @@ Failure: --- +### Create agent + +**POST** `/api/v1/agents` + +Create an agent. + +#### Request + +- Method: POST +- URL: `/api/v1/agents` +- Headers: + - `'Content-Type: application/json` + - `'Authorization: Bearer '` +- Body: + - `"title"`: `string` + - `"description"`: `string` + - `"dsl"`: `object` + +##### Request example + +```bash +curl --request POST \ + --url http://{address}/api/v1/agents \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{ + "title": "Test Agent", + "description": "A test agent", + "dsl": { + // ... Canvas DSL here ... + } + }' +``` + +##### Request parameters + +- `title`: (*Body parameter*), `string`, *Required* + The title of the agent. +- `description`: (*Body parameter*), `string` + The description of the agent. Defaults to `None`. +- `dsl`: (*Body parameter*), `object`, *Required* + The canvas DSL object of the agent. + +#### Response + +Success: + +```json +{ + "code": 0, + "data": true, + "message": "success" +} +``` + +Failure: + +```json +{ + "code": 102, + "message": "Agent with title test already exists." +} +``` + +--- + +### Update agent + +**PUT** `/api/v1/agents/{agent_id}` + +Update an agent by id. + +#### Request + +- Method: PUT +- URL: `/api/v1/agents/{agent_id}` +- Headers: + - `'Content-Type: application/json` + - `'Authorization: Bearer '` +- Body: + - `"title"`: `string` + - `"description"`: `string` + - `"dsl"`: `object` + +##### Request example + +```bash +curl --request PUT \ + --url http://{address}/api/v1/agents/58af890a2a8911f0a71a11b922ed82d6 \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{ + "title": "Test Agent", + "description": "A test agent", + "dsl": { + // ... Canvas DSL here ... + } + }' +``` + +##### Request parameters + +- `agent_id`: (*Path parameter*), `string` + The id of the agent to be updated. +- `title`: (*Body parameter*), `string` + The title of the agent. +- `description`: (*Body parameter*), `string` + The description of the agent. +- `dsl`: (*Body parameter*), `object` + The canvas DSL object of the agent. + +Only specify the parameter you want to change in the request body. If a parameter does not exist or is `None`, it won't be updated. + +#### Response + +Success: + +```json +{ + "code": 0, + "data": true, + "message": "success" +} +``` + +Failure: + +```json +{ + "code": 103, + "message": "Only owner of canvas authorized for this operation." +} +``` + +--- + +### Delete agent + +**DELETE** `/api/v1/agents/{agent_id}` + +Delete an agent by id. + +#### Request + +- Method: DELETE +- URL: `/api/v1/agents/{agent_id}` +- Headers: + - `'Content-Type: application/json` + - `'Authorization: Bearer '` + +##### Request example + +```bash +curl --request DELETE \ + --url http://{address}/api/v1/agents/58af890a2a8911f0a71a11b922ed82d6 \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer ' \ + --data '{}' +``` + +##### Request parameters + +- `agent_id`: (*Path parameter*), `string` + The id of the agent to be deleted. + +#### Response + +Success: + +```json +{ + "code": 0, + "data": true, + "message": "success" +} +``` + +Failure: + +```json +{ + "code": 103, + "message": "Only owner of canvas authorized for this operation." +} +``` + +--- diff --git a/docs/references/python_api_reference.md b/docs/references/python_api_reference.md index be827f720..46631bbb3 100644 --- a/docs/references/python_api_reference.md +++ b/docs/references/python_api_reference.md @@ -1754,4 +1754,133 @@ for agent in rag_object.list_agents(): --- +### Create agent +```python +RAGFlow.create_agent( + title: str, + dsl: dict, + description: str | None = None +) -> None +``` + +Create an agent. + +#### Parameters + +##### title: `str` + +Specifies the title of the agent. + +##### dsl: `dict` + +Specifies the canvas DSL of the agent. + +##### description: `str` + +The description of the agent. Defaults to `None`. + +#### Returns + +- Success: Nothing. +- Failure: `Exception`. + +#### Examples + +```python +from ragflow_sdk import RAGFlow +rag_object = RAGFlow(api_key="", base_url="http://:9380") +rag_object.create_agent( + title="Test Agent", + description="A test agent", + dsl={ + # ... canvas DSL here ... + } +) +``` + +--- + +### Update agent + +```python +RAGFlow.update_agent( + agent_id: str, + title: str | None = None, + description: str | None = None, + dsl: dict | None = None +) -> None +``` + +Update an agent. + +#### Parameters + +##### agent_id: `str` + +Specifies the id of the agent to be updated. + +##### title: `str` + +Specifies the new title of the agent. `None` if you do not want to update this. + +##### dsl: `dict` + +Specifies the new canvas DSL of the agent. `None` if you do not want to update this. + +##### description: `str` + +The new description of the agent. `None` if you do not want to update this. + +#### Returns + +- Success: Nothing. +- Failure: `Exception`. + +#### Examples + +```python +from ragflow_sdk import RAGFlow +rag_object = RAGFlow(api_key="", base_url="http://:9380") +rag_object.update_agent( + agent_id="58af890a2a8911f0a71a11b922ed82d6", + title="Test Agent", + description="A test agent", + dsl={ + # ... canvas DSL here ... + } +) +``` + +--- + +### Delete agent + +```python +RAGFlow.delete_agent( + agent_id: str +) -> None +``` + +Delete an agent. + +#### Parameters + +##### agent_id: `str` + +Specifies the id of the agent to be deleted. + +#### Returns + +- Success: Nothing. +- Failure: `Exception`. + +#### Examples + +```python +from ragflow_sdk import RAGFlow +rag_object = RAGFlow(api_key="", base_url="http://:9380") +rag_object.delete_agent("58af890a2a8911f0a71a11b922ed82d6") +``` + +--- diff --git a/sdk/python/ragflow_sdk/ragflow.py b/sdk/python/ragflow_sdk/ragflow.py index 9b6267cdd..a2569353c 100644 --- a/sdk/python/ragflow_sdk/ragflow.py +++ b/sdk/python/ragflow_sdk/ragflow.py @@ -244,3 +244,49 @@ class RAGFlow: result_list.append(Agent(self, data)) return result_list raise Exception(res["message"]) + + def create_agent(self, title: str, dsl: dict, description: str | None = None) -> None: + req = { + "title": title, + "dsl": dsl + } + + if description is not None: + req["description"] = description + + res = self.post("/agents", req) + res = res.json() + + if res.get("code") != 0: + raise Exception(res["message"]) + + def update_agent( + self, + agent_id: str, + title: str | None = None, + description: str | None = None, + dsl: dict | None = None + ) -> None: + req = {} + + if title is not None: + req["title"] = title + + if description is not None: + req["description"] = description + + if dsl is not None: + req["dsl"] = dsl + + res = self.put(f"/agents/{agent_id}", req) + res = res.json() + + if res.get("code") != 0: + raise Exception(res["message"]) + + def delete_agent(self, agent_id: str) -> None: + res = self.delete(f"/agents/{agent_id}", {}) + res = res.json() + + if res.get("code") != 0: + raise Exception(res["message"])