### What problem does this PR solve? Fix bugs in agent api and update api document ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: liuhua <10215101452@stu.ecun.edu.cn>tags/v0.15.0
| @@ -185,6 +185,7 @@ class Canvas(ABC): | |||
| self.path.append(["begin"]) | |||
| self.path.append([]) | |||
| ran = -1 | |||
| waiting = [] | |||
| without_dependent_checking = [] | |||
| @@ -73,6 +73,8 @@ def create_agent_session(tenant_id, agent_id): | |||
| cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False) | |||
| canvas = Canvas(cvs.dsl, tenant_id) | |||
| if canvas.get_preset_param(): | |||
| return get_error_data_result("The agent can't create a session directly") | |||
| conv = { | |||
| "id": get_uuid(), | |||
| "dialog_id": cvs.id, | |||
| @@ -112,6 +114,8 @@ def update(tenant_id, chat_id, session_id): | |||
| @token_required | |||
| def chat_completion(tenant_id, chat_id): | |||
| req = request.json | |||
| if not req or not req.get("session_id"): | |||
| req = {"question":""} | |||
| if not DialogService.query(tenant_id=tenant_id,id=chat_id,status=StatusEnum.VALID.value): | |||
| return get_error_data_result(f"You don't own the chat {chat_id}") | |||
| if req.get("session_id"): | |||
| @@ -125,7 +129,6 @@ def chat_completion(tenant_id, chat_id): | |||
| resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") | |||
| return resp | |||
| else: | |||
| answer = None | |||
| for ans in rag_completion(tenant_id, chat_id, **req): | |||
| @@ -137,22 +140,28 @@ def chat_completion(tenant_id, chat_id): | |||
| @manager.route('/agents/<agent_id>/completions', methods=['POST']) # noqa: F821 | |||
| @token_required | |||
| def agent_completions(tenant_id, agent_id): | |||
| req = request.json | |||
| if not UserCanvasService.query(user_id=tenant_id,id=agent_id): | |||
| return get_error_data_result(f"You don't own the agent {agent_id}") | |||
| if req.get("session_id"): | |||
| if not API4ConversationService.query(id=req["session_id"],dialog_id=agent_id): | |||
| return get_error_data_result(f"You don't own the session {req['session_id']}") | |||
| if req.get("stream", True): | |||
| resp = Response(agent_completion(tenant_id, agent_id, **req), mimetype="text/event-stream") | |||
| resp.headers.add_header("Cache-control", "no-cache") | |||
| resp.headers.add_header("Connection", "keep-alive") | |||
| resp.headers.add_header("X-Accel-Buffering", "no") | |||
| resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") | |||
| return resp | |||
| for answer in agent_completion(tenant_id, agent_id, **req): | |||
| return get_result(data=answer) | |||
| req = request.json | |||
| cvs = UserCanvasService.query(user_id=tenant_id, id=agent_id) | |||
| if not cvs: | |||
| return get_error_data_result(f"You don't own the agent {agent_id}") | |||
| if req.get("session_id"): | |||
| conv = API4ConversationService.query(id=req["session_id"], dialog_id=agent_id) | |||
| if not conv: | |||
| return get_error_data_result(f"You don't own the session {req['session_id']}") | |||
| else: | |||
| req["question"]="" | |||
| if req.get("stream", True): | |||
| resp = Response(agent_completion(tenant_id, agent_id, **req), mimetype="text/event-stream") | |||
| resp.headers.add_header("Cache-control", "no-cache") | |||
| resp.headers.add_header("Connection", "keep-alive") | |||
| resp.headers.add_header("X-Accel-Buffering", "no") | |||
| resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") | |||
| return resp | |||
| try: | |||
| for answer in agent_completion(tenant_id, agent_id, **req): | |||
| return get_result(data=answer) | |||
| except Exception as e: | |||
| return get_error_data_result(str(e)) | |||
| @manager.route('/chats/<chat_id>/sessions', methods=['GET']) # noqa: F821 | |||
| @@ -420,3 +429,5 @@ def agent_bot_completions(agent_id): | |||
| for answer in agent_completion(objs[0].tenant_id, agent_id, **req): | |||
| return get_result(data=answer) | |||
| @@ -55,36 +55,39 @@ def completion(tenant_id, agent_id, question, session_id=None, stream=True, **kw | |||
| e, cvs = UserCanvasService.get_by_id(agent_id) | |||
| assert e, "Agent not found." | |||
| assert cvs.user_id == tenant_id, "You do not own the agent." | |||
| if not isinstance(cvs.dsl, str): | |||
| if not isinstance(cvs.dsl,str): | |||
| cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False) | |||
| canvas = Canvas(cvs.dsl, tenant_id) | |||
| canvas.reset() | |||
| message_id = str(uuid4()) | |||
| if not session_id: | |||
| query = canvas.get_preset_param() | |||
| if query: | |||
| for ele in query: | |||
| if not ele["optional"]: | |||
| if not kwargs.get(ele["key"]): | |||
| assert False, f"`{ele['key']}` is required" | |||
| ele["value"] = kwargs[ele["key"]] | |||
| if ele["optional"]: | |||
| if kwargs.get(ele["key"]): | |||
| ele["value"] = kwargs[ele['key']] | |||
| else: | |||
| if "value" in ele: | |||
| ele.pop("value") | |||
| cvs.dsl = json.loads(str(canvas)) | |||
| temp_dsl = cvs.dsl | |||
| UserCanvasService.update_by_id(agent_id, cvs.to_dict()) | |||
| else: | |||
| temp_dsl = json.loads(cvs.dsl) | |||
| session_id = get_uuid() | |||
| conv = { | |||
| "id": session_id, | |||
| "dialog_id": cvs.id, | |||
| "user_id": kwargs.get("user_id", ""), | |||
| "source": "agent", | |||
| "dsl": json.loads(cvs.dsl) | |||
| "dsl": temp_dsl | |||
| } | |||
| API4ConversationService.save(**conv) | |||
| if canvas.get_preset_param(): | |||
| yield "data:" + json.dumps({"code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "session_id": session_id, | |||
| "answer": "", | |||
| "reference": [], | |||
| "param": canvas.get_preset_param() | |||
| } | |||
| }, | |||
| ensure_ascii=False) + "\n\n" | |||
| yield "data:" + json.dumps({"code": 0, "message": "", "data": True}, ensure_ascii=False) + "\n\n" | |||
| return | |||
| conv = API4Conversation(**conv) | |||
| else: | |||
| e, conv = API4ConversationService.get_by_id(session_id) | |||
| @@ -104,7 +107,6 @@ def completion(tenant_id, agent_id, question, session_id=None, stream=True, **kw | |||
| conv.reference.append({"chunks": [], "doc_aggs": []}) | |||
| final_ans = {"reference": [], "content": ""} | |||
| if stream: | |||
| try: | |||
| for ans in canvas.run(stream=stream): | |||
| @@ -86,8 +86,9 @@ def completion(tenant_id, chat_id, question, name="New session", session_id=None | |||
| assert dia, "You do not own the chat." | |||
| if not session_id: | |||
| session_id = get_uuid() | |||
| conv = { | |||
| "id": get_uuid(), | |||
| "id":session_id , | |||
| "dialog_id": chat_id, | |||
| "name": name, | |||
| "message": [{"role": "assistant", "content": dia[0].prompt_config.get("prologue")}] | |||
| @@ -2015,11 +2015,20 @@ curl --request POST \ | |||
| --header 'Authorization: Bearer <YOUR_API_KEY>' \ | |||
| --data-binary ' | |||
| { | |||
| "question": "What is RAGFlow?", | |||
| "stream": true | |||
| }' | |||
| ``` | |||
| ```bash | |||
| curl --request POST \ | |||
| --url http://{address}/api/v1/chats/{chat_id}/completions \ | |||
| --header 'Content-Type: application/json' \ | |||
| --header 'Authorization: Bearer <YOUR_API_KEY>' \ | |||
| --data-binary ' | |||
| { | |||
| "question": "Who are you", | |||
| "stream": true, | |||
| "session_id":"9fa7691cb85c11ef9c5f0242ac120005" | |||
| }' | |||
| ``` | |||
| #### Request Parameters | |||
| - `chat_id`: (*Path parameter*) | |||
| @@ -2034,10 +2043,29 @@ curl --request POST \ | |||
| The ID of session. If it is not provided, a new session will be generated. | |||
| ### Response | |||
| Success without `session_id`: | |||
| ```text | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "Hi! I'm your assistant, what can I do for you?", | |||
| "reference": {}, | |||
| "audio_binary": null, | |||
| "id": null, | |||
| "session_id": "b01eed84b85611efa0e90242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": true | |||
| } | |||
| ``` | |||
| Success: | |||
| Success with `session_id`: | |||
| ```json | |||
| ```text | |||
| data:{ | |||
| "code": 0, | |||
| "data": { | |||
| @@ -2121,6 +2149,7 @@ Failure: | |||
| --- | |||
| ## Create session with agent | |||
| *If there are parameters in the `begin` component, the session cannot be created in this way.* | |||
| **POST** `/api/v1/agents/{agent_id}/sessions` | |||
| @@ -2159,16 +2188,101 @@ Success: | |||
| { | |||
| "code": 0, | |||
| "data": { | |||
| "agent_id": "2e45b5209c1011efa3e90242ac120006", | |||
| "id": "7869e9e49c1711ef92840242ac120006", | |||
| "agent_id": "b4a39922b76611efaa1a0242ac120006", | |||
| "dsl": { | |||
| "answer": [], | |||
| "components": { | |||
| "Answer:GreenReadersDrum": { | |||
| "downstream": [], | |||
| "obj": { | |||
| "component_name": "Answer", | |||
| "inputs": [], | |||
| "output": null, | |||
| "params": {} | |||
| }, | |||
| "upstream": [] | |||
| }, | |||
| "begin": { | |||
| "downstream": [], | |||
| "obj": { | |||
| "component_name": "Begin", | |||
| "inputs": [], | |||
| "output": {}, | |||
| "params": {} | |||
| }, | |||
| "upstream": [] | |||
| } | |||
| }, | |||
| "embed_id": "", | |||
| "graph": { | |||
| "edges": [], | |||
| "nodes": [ | |||
| { | |||
| "data": { | |||
| "label": "Begin", | |||
| "name": "begin" | |||
| }, | |||
| "dragging": false, | |||
| "height": 44, | |||
| "id": "begin", | |||
| "position": { | |||
| "x": 53.25688640427177, | |||
| "y": 198.37155679786412 | |||
| }, | |||
| "positionAbsolute": { | |||
| "x": 53.25688640427177, | |||
| "y": 198.37155679786412 | |||
| }, | |||
| "selected": false, | |||
| "sourcePosition": "left", | |||
| "targetPosition": "right", | |||
| "type": "beginNode", | |||
| "width": 200 | |||
| }, | |||
| { | |||
| "data": { | |||
| "form": {}, | |||
| "label": "Answer", | |||
| "name": "对话_0" | |||
| }, | |||
| "dragging": false, | |||
| "height": 44, | |||
| "id": "Answer:GreenReadersDrum", | |||
| "position": { | |||
| "x": 360.43473114516974, | |||
| "y": 207.29298425089348 | |||
| }, | |||
| "positionAbsolute": { | |||
| "x": 360.43473114516974, | |||
| "y": 207.29298425089348 | |||
| }, | |||
| "selected": false, | |||
| "sourcePosition": "right", | |||
| "targetPosition": "left", | |||
| "type": "logicNode", | |||
| "width": 200 | |||
| } | |||
| ] | |||
| }, | |||
| "history": [], | |||
| "messages": [], | |||
| "path": [ | |||
| [ | |||
| "begin" | |||
| ], | |||
| [] | |||
| ], | |||
| "reference": [] | |||
| }, | |||
| "id": "2581031eb7a311efb5200242ac120005", | |||
| "message": [ | |||
| { | |||
| "content": "Hello! I am a recruiter at InfiniFlow. I learned that you are an expert in the field, and took the liberty of reaching out to you. There is an opportunity I would like to share with you. RAGFlow is currently looking for a senior engineer for your position. I was wondering if you might be interested?", | |||
| "content": "Hi! I'm your smart assistant. What can I do for you?", | |||
| "role": "assistant" | |||
| } | |||
| ], | |||
| "source": "agent", | |||
| "user_id": "" | |||
| "user_id": "69736c5e723611efb51b0242ac120007" | |||
| } | |||
| } | |||
| ``` | |||
| @@ -2216,7 +2330,7 @@ Asks a specified agent a question to start an AI-powered conversation. | |||
| - `"question"`: `string` | |||
| - `"stream"`: `boolean` | |||
| - `"session_id"`: `string` | |||
| - other parameters: `string` | |||
| #### Request example | |||
| ```bash | |||
| @@ -2226,11 +2340,33 @@ curl --request POST \ | |||
| --header 'Authorization: Bearer <YOUR_API_KEY>' \ | |||
| --data-binary ' | |||
| { | |||
| "question": "What is RAGFlow?", | |||
| "stream": true | |||
| }' | |||
| ``` | |||
| ```bash | |||
| curl --request POST \ | |||
| --url http://{address}/api/v1/agents/{agent_id}/completions \ | |||
| --header 'Content-Type: application/json' \ | |||
| --header 'Authorization: Bearer <YOUR_API_KEY>' \ | |||
| --data-binary ' | |||
| { | |||
| "question": "Hello", | |||
| "stream": true, | |||
| "session_id": "cb2f385cb86211efa36e0242ac120005" | |||
| }' | |||
| ``` | |||
| ```bash | |||
| curl --request POST \ | |||
| --url http://{address}/api/v1/agents/{agent_id}/completions \ | |||
| --header 'Content-Type: application/json' \ | |||
| --header 'Authorization: Bearer <YOUR_API_KEY>' \ | |||
| --data-binary ' | |||
| { | |||
| "lang":"English" | |||
| "file":"明天天气如何" | |||
| }' | |||
| ``` | |||
| #### Request Parameters | |||
| - `agent_id`: (*Path parameter*), `string` | |||
| @@ -2243,10 +2379,28 @@ curl --request POST \ | |||
| - `false`: Disable streaming. | |||
| - `"session_id"`: (*Body Parameter*) | |||
| The ID of the session. If it is not provided, a new session will be generated. | |||
| - Other parameters: (*Body Parameter*) | |||
| The parameters in the begin component. | |||
| ### Response | |||
| Success: | |||
| success without `session_id` provided and with no parameters in the `begin` component: | |||
| ```text | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "Hi! I'm your smart assistant. What can I do for you?", | |||
| "reference": {}, | |||
| "id": "31e6091d-88d4-441b-ac65-eae1c055be7b", | |||
| "session_id": "2987ad3eb85f11efb2a70242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": true | |||
| } | |||
| ``` | |||
| Success with `session_id` provided and with no parameters in the `begin` component: | |||
| ```text | |||
| data:{ | |||
| @@ -2354,6 +2508,85 @@ data:{ | |||
| "data": true | |||
| } | |||
| ``` | |||
| Success with parameters in the `begin` component: | |||
| ```text | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is the", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is the weather", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is the weather tomorrow", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is the weather tomorrow?", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": { | |||
| "answer": "How is the weather tomorrow?", | |||
| "reference": {}, | |||
| "id": "0379ac4c-b26b-4a44-8b77-99cebf313fdf", | |||
| "session_id": "4399c7d0b86311efac5b0242ac120005" | |||
| } | |||
| } | |||
| data:{ | |||
| "code": 0, | |||
| "message": "", | |||
| "data": true | |||
| } | |||
| ``` | |||
| Failure: | |||
| @@ -14,6 +14,13 @@ Dataset Management | |||
| ::: | |||
| --- | |||
| ### Install the RAGFlow SDK | |||
| To install the RAGFlow SDK, run the following command in your terminal: | |||
| ```bash | |||
| pip install ragflow-sdk | |||
| ``` | |||
| ## Create dataset | |||
| @@ -1401,7 +1408,7 @@ while True: | |||
| --- | |||
| ## Create session with agent | |||
| *If there are parameters in the `begin` component, the session cannot be created in this way.* | |||
| ```python | |||
| Agent.create_session(id,rag) -> Session | |||
| ``` | |||
| @@ -1428,7 +1435,7 @@ session = create_session(AGENT_ID,rag_object) | |||
| --- | |||
| ## Converse with agent | |||
| ## Converse with agent without `begin` component | |||
| ```python | |||
| Session.ask(question: str, stream: bool = False) -> Optional[Message, iter[Message]] | |||
| @@ -1,7 +1,9 @@ | |||
| from .base import Base | |||
| from .session import Session | |||
| from .session import Session,Message | |||
| import requests | |||
| from typing import List | |||
| import json | |||
| class Agent(Base): | |||
| def __init__(self,rag,res_dict): | |||
| @@ -73,3 +75,30 @@ class Agent(Base): | |||
| result_list.append(temp_agent) | |||
| return result_list | |||
| raise Exception(res.get("message")) | |||
| @staticmethod | |||
| def ask(agent_id,rag,stream=True,**kwargs): | |||
| url = f"{rag.api_url}/agents/{agent_id}/completions" | |||
| headers = {"Authorization": f"Bearer {rag.user_key}"} | |||
| res = requests.post(url=url, headers=headers, json=kwargs,stream=stream) | |||
| for line in res.iter_lines(): | |||
| line = line.decode("utf-8") | |||
| if line.startswith("{"): | |||
| json_data = json.loads(line) | |||
| raise Exception(json_data["message"]) | |||
| if line.startswith("data:"): | |||
| json_data = json.loads(line[5:]) | |||
| if json_data["data"] is not True: | |||
| if json_data["data"].get("running_status"): | |||
| continue | |||
| answer = json_data["data"]["answer"] | |||
| reference = json_data["data"]["reference"] | |||
| temp_dict = { | |||
| "content": answer, | |||
| "role": "assistant" | |||
| } | |||
| if "chunks" in reference: | |||
| chunks = reference["chunks"] | |||
| temp_dict["reference"] = chunks | |||
| message = Message(rag, temp_dict) | |||
| yield message | |||
| @@ -29,7 +29,7 @@ class Session(Base): | |||
| raise Exception(json_data["message"]) | |||
| if line.startswith("data:"): | |||
| json_data = json.loads(line[5:]) | |||
| if not json_data["data"]: | |||
| if json_data["data"] is not True: | |||
| answer = json_data["data"]["answer"] | |||
| reference = json_data["data"]["reference"] | |||
| temp_dict = { | |||
| @@ -1,4 +1,4 @@ | |||
| from ragflow_sdk import RAGFlow | |||
| from ragflow_sdk import RAGFlow,Agent | |||
| from common import HOST_ADDRESS | |||
| import pytest | |||
| @@ -6,4 +6,14 @@ import pytest | |||
| def test_list_agents_with_success(get_api_key_fixture): | |||
| API_KEY=get_api_key_fixture | |||
| rag = RAGFlow(API_KEY,HOST_ADDRESS) | |||
| rag.list_agents() | |||
| rag.list_agents() | |||
| @pytest.mark.skip(reason="") | |||
| def test_converse_with_agent_with_success(get_api_key_fixture): | |||
| API_KEY = "ragflow-BkOGNhYjIyN2JiODExZWY5MzVhMDI0Mm" | |||
| agent_id = "ebfada2eb2bc11ef968a0242ac120006" | |||
| rag = RAGFlow(API_KEY,HOST_ADDRESS) | |||
| lang = "Chinese" | |||
| file = "How is the weather tomorrow?" | |||
| Agent.ask(agent_id=agent_id,rag=rag,lang=lang,file=file) | |||